最近の更新 (Recent Changes)

2014-01-01
2013-01-04
2012-12-22
2012-12-15
2012-12-09

Wikiガイド(Guide)

サイドバー (Side Bar)

--
← 前のページに戻る

4. VM(バーチャル・マシン)の仕様

さて、ここからは、Closure BasicのVM(バーチャル・マシン)の仕様について説明していきましょう。 JAVAやC#(.Net)も、VMのコードにコンパイルされてから、実行されますね。 Closure Basicも同様にVMを使って、プログラムのコンパイル結果を実行します。

以降では、このClosure Basicのコンパイル結果を実行するVMをCLVMと呼ぶこととします。

Closure Basicのプログラムは、コンパイルされるとCLVMの命令コードに変換されます。 そのCLVM命令コードは、CLVMの上で実際のコンピューターの命令として実行されることになるのです。

4.1 CLVMのアーキテクチャ

Closure BasicのCLVMのアーキテクチャーの構成要素は、大きく分けるとスタックとクロージャに分かれます。 クロージャは、Closure0から始まり、定義された関数一つごとにコンパイル時に生成されます。


+CLVM-------------------+
|                       |
|  Stack                |
|                       |
|  Closure0             |
|  Closure1             |
|   ...                 |
|                       |
|                       |
|                       |
+-----------------------+


命令コードや変数の値を保持するメモリ領域は、すべてクロージャー内にのみ存在します。

メモリからの値の読み込み・書き込み、演算はスタックで、すべて行います。

4.1.1 スタック・マシン

Closure Basicのバーチャル・マシンであるCLVMのアーキテクチャは、スタック・マシンです。

最近はリアルなマシンであるx86とかRISCとかでは、レジスタ・マシンが全盛です。 深いパイプラインを持ち、次々に先行命令をスーパースカラーで実行していくには、それが必然なのでしょう。

しかし、中間コード方式のJAVAや.NETはスタック・マシンですね。

それは、ソフトで実現する中間コード・インタプリターでは、スタック・マシンに充分なメリットがあるからだと考えます。

Closure Basicでスタック・マシンを採用したのは次のような理由からです。

- スタック・マシンは、コンパクトなアーキテクチャであり、中間コードのインタプリタを効率よく作成できます。 レジスタを持たず、システムに一つあるだけの演算実行用のスタックを使ってすべての処理を実行できます。

- 少ない命令数でも、多彩なプログラミングが可能です。 メモリを経由しなくても、スタック上で演算を次々に実行していくことによりプログラムが進んでいきます。

- 命令数が少ないうえに、さらに命令コードにレジスタ番号を指定する必要がなくなるので、同じプログラムでも命令コードを短くすることが可能です。

Closure Basicのスタック(演算用)はシステムに1本だけ存在し、全クロージャから共通に使われます。

スタックの長さにはとくに制限を設けていません。複雑な演算でもOKです。 スタックから値を取り出しつくてアンダーフローした場合には、CLVMに異常イベントが上がってプログラムの実行は異常終了します。

4.1.2 メモリモデル

CLVMには、一般のコンピューター・アーキテクチャーと異なり、共通のアドレス番地は持ちません。 また使われる領域ごとにまったく別領域であり、使われ方も厳密に区別されます。

命令コード・メモリ領域と変数の値を保持するデータ領域・メモリ領域は、すべてクロージャ内に存在します。 加えて、クロージャ内にはクロージャを呼び出したときにパラメターに対応する変数を保存するパラメター・スタックも 含まれます。パラメター・スタックは前項で説明したスタック(演算用)とは別のスタックです。

このあたりのクロージャについての説明は次の項から詳しく説明する予定なので楽しみにしていてください。


クロージャ
  命令コード・メモリ領域
  データ領域・メモリ領域
  パラメター・スタック

メモリモデルとしては、命令コード、データ領域とパラメター・スタックは厳密に区別された領域に分けて格納します。

命令コード領域とパラメター・スタックは、所属するクロージャでしか使われません。 クロージャ外の他のクロージャからはアクセスする手段はありません。

データ領域については、クロージャ名、アドレス番号の組の値によって、クロージャ外の他のクロージャからアクセスできるようにします。 アドレス番号は、データ領域の先頭0からのオフセットです。クロージャ内で使われる領域サイズ分確保され、変数が増えた場合には動的に拡張できます。


データのアクセスのための対組

  [クロージャ名、アドレス番号]

CLVMの命令では、データ領域のメモリをアクセスするときには必ずこの対組によってアクセスします。

4.2 クロージャのアーキテクチャ

Closure Basicのクロージャは以下のような要素で構成されています。


クロージャの要素

  PC(プログラム・カウンター)

  パラメター数

  命令コード・メモリ領域

  データ領域・メモリ領域

  パラメター・スタック

Closure Basicでは、定義された関数がコンパイルされると、必ず対応するクロージャが新たに生成され、上に示すセットが設置されます。

コンパイル時には、次の処理が行われます。

1) 命令コード・メモリ領域には、コンパイルされた結果である関数内の処理の命令コードが設定されます。

2) パラメター数には、関数の引数パラメターの数がコンパイル時に設定されます。

実行時には、次の処理が行われます。

1) プログラム実行時に、この関数が呼び出されると、まずPCに命令コード・アドレスとして0が設定されます。

2) 関数(クロージャ)への呼び出し引数は、スタック(演算用)に、積んでおいて呼び出します。 パラメター・スタックではないことに注意してください。

3) そして、次にパラメター変数に相当する変数の値がパラメター・スタックに退避されます。 パラメター・スタックは、パラメター変数の退避に使われ、クロージャが呼ばれるたびに、どんどんと値が退避されて詰まれて生きます。

4) システム共通のスタック(演算用)から、クロージャに設定されているパラメター数分の値をパラメター変数に設定する命令コードが実行されます。 このパラメター変数への引数の設定処理は、コンパイル時に処理する命令コードとしてクロージャの命令コード・メモリ領域の最初の領域に格納してあります。

5) データ領域の値を使うだけ領域を拡張する命令コードが実行されます。 この拡張命令はクロージャが最初に呼び出されたときに実行されるとデータ領域を拡張します。 すでにクロージャが1度呼び出された後には、この命令コードは実行されても何もしないで次の処理を行います。 この拡張命令も、コンパイル時に命令コードとして格納されたものです。

6) 命令コードにしたがって、処理を続行していきます。 CALL命令により、他のクロージャを呼ぶときには、このクロージャの状態はそのまま保持されます。

7) クロージャの処理が完了するときには、スタック(演算用)に返り値を設定してRET命令を実行します。 復帰するときには、パラメタースタックから、このクロージャが呼び出されたときに保存した値をリストアして呼び出し元に返ります。

クロージャは、処理が完了して復帰しても、内部に含む要素はそのまま保持されます。 次に同じクロージャが呼び出されると、データ領域上の変数はそのまま残っているので、以前に呼び出されたときの値のままです。 これが、他のクロージャを採用しないプログラム言語との大きな相違となっています。

4.2.1 オブジェクトとしてのクロージャ

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オブジェクトがクローンされます。 この場合は、継承するのではないことに注意してください。 新たに作成されたクロージャは独立して独自のコードと環境を持ったクロージャとなります。

4.2.1.1 PC

実行中の命令コードのアドレス位置を示す整数値を保存します。

4.2.1.2 パラメター数

関数(クロージャ)の引数の数を整数値で保存しています。 実際に引数としてスタック(演算用)に渡された引数の数と比較され、異なっている場合にはエラーになります。

4.2.1.3 パラメター・スタック

関数(クロージャ)が呼ばれるたびに、引数の変数の値を保存していきます。

関数(クロージャ)が復帰するときには、保存された引数の値が復元されます。

4.2.1.4 命令コード・メモリ領域

命令コードを、リスト形式で保存しています。

命令コードは、文字列アトムで表現されています。


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の命令コード"で説明します。

4.2.1.5 データ領域・メモリ領域

変数のデータをリスト形式で保存します。

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が入っています。

4.3 謎のクロージャ(closure0)

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バーチャル・マシン上ではすべてクロージャ上の処理とデータとして統一して扱われるようになっています。

4.4 クロージャのスコープ

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. コンパイラの仕様」で詳しく解説します。

4.5 CLVMの命令コード

CLVMのバーチャル・マシンの全命令コードを、スタック操作命令、演算命令、制御命令、call/return/save/restore命令、動的メモリ拡張命令、input/output命令、およびその他命令に分類して説明していきます。

なお、命令コードには引数(オペランド)を持たない命令とオペランドを1つ持つ命令の2種類があります。


  命令コード

  命令コード  オペランド

オペランドには数値、文字列、アドレス、あるいはクロージャなどを指定することになります。

オペランドがない命令は、スタックの上の値をもとに操作するものや、引数の値が不要な命令です。

4.5.1 スタック操作命令

PUSHI オペランド

PUSHI命令は、オペランドに指定された数値、文字列、アドレス、あるいはクロージャをスタックにプッシュします。
プッシュされた値は、スタックの一番上に追加されます。


   使用例
     PUSHI 10
     PUSHI "ABC"
     PUSHI closure1

PUSH

スタック上の、クロージャとアドレスを使い、、[クロージャ名、アドレス番号]に相当するデータ領域の値をスタックにプッシュします。
スタック上に事前にクロージャとアドレスを積んでおき、このPUSH命令を実行します。
その結果スタックはクロージャとアドレス相当分短くなった後に、新たに指定されたデータ領域の値がスタック上に追加されます。


   使用例
     PUSHI closure1
     PUSHI 10
     PUSH

closure1のアドレス10の指すデータ領域の値をスタックにプッシュしています。

POP

スタック上の、クロージャとアドレスを使い、、[クロージャ名、アドレス番号]に相当するデータ領域にスタックから値を取り出して設定します。
スタック上に事前にデータ領域に設定する値とクロージャとアドレスを積んでおき、このPOP命令を実行します。
その結果スタックはクロージャ、アドレス、および取り出した値に相当する分短かくなります。


   使用例
     PUSHI 123
     PUSHI closure1
     PUSHI 10
     POP

closure1のアドレス10の指すデータ領域に値123をスタックから取り出して設定しています。

DUP

スタックの一番上(トップ)の値を、複製して新たにスタックにプッシュします。
スタックの上には、同じ値が2つ並びます。一時的にスタックのトップの値を保存しておいて、複製した値を使って計算などの処理を行うのに使います。


   使用例
     PUSHI 3
     DUP

スタックの上には、値3が二つ入ります。

DUP2

スタックの一番上と二番目の値をまとめて複製して、新たにスタックに2つの値をプッシュします。
スタックに、値1, 値2とあった場合、DUP2命令の実行後にはスタック上に、値1, 値2, 値1, 値2 と積まれることになります。
この命令はCLVMでは、主にスタック上に[クロージャ名、アドレス番号]が積まれていた場合に、その2つの値の組をまとめて複製するのに使われます。


   使用例
     PUSHI closure1
     PUSHI 11
     DUP2

スタックの上には、closure1, 11, closure1, 11 と入ります。

DROP

スタックの一番上の値を捨てて、スタックを一つ縮めます。
演算結果の結果が不要な場合等に、値を捨てるのに使います。


   使用例
     PUSHI 11
     DROP

スタックの上にPUSHI命令で積まれた値11は、DROP命令によって捨てられ、スタック上から消えます。

SWAP

スタックの1番上の値と、2番目の値を入れ替えます。
実行後でもスタックの長さは変わりません。


   使用例
     PUSHI 11
     PUSHI 22
     SWAP

スタックの上にPUSHI命令で積まれた値11と値22は、SWAP命令によって入れ替えられます。
11, 22とスタックに積まれていた値は、SWAP命令実行後には、22, 11と順番が入れ替えられます。

ROT

スタックの一番上の値を取り出し、スタックの3番目に挿入します。


   使用例
     PUSHI 11
     PUSHI 22
     PUSHI 33
     ROT

11, 22, 33とスタックに積まれていた値は、ROT命令実行後には、33, 11, 22と順番が入れ替えられます。

4.5.2 演算命令

ADD

スタックの2番上の値と、1番目の値を足して、その結果をスタックにプッシュします。


   使用例
     PUSHI 11
     PUSHI 22
     ADD

スタックの上にPUSHI命令で積まれた値11と値22は、ADD命令によって足されます。
結果の33がスタックに積まれます。

SUB

スタックの2番上の値から、1番目の値を引いて、その結果をスタックにプッシュします。


   使用例
     PUSHI 11
     PUSHI 22
     SUB

スタックの上にPUSHI命令で積まれた値11と値22は、SUB命令によって引き算されます。
結果の-11がスタックに積まれます。

MUL

スタックの2番上の値と、1番目の値を掛けて、その結果をスタックにプッシュします。


   使用例
     PUSHI 11
     PUSHI 22
     MUL

スタックの上にPUSHI命令で積まれた値11と値22は、MUL命令によって足されます。
結果の242がスタックに積まれます。

DIV

スタックの2番上の値から、1番目の値を割って、その結果をスタックにプッシュします。
なお、0で割り算するとCLVMが異常イベントを発生させて異常終了します。


   使用例
     PUSHI 11
     PUSHI 22
     DIV

スタックの上にPUSHI命令で積まれた値11と値22は、DIV命令によって引き算されます。
結果の0.5がスタックに積まれます。

INV

スタックの1番目の値の符号を反転させます。
正の値なら負、負の値なら正となります。


   使用例
     PUSHI 11
     INV

スタックの上にPUSHI命令で積まれた値11は、INV命令によって-11と鳴ります。

CMPE

スタックの2番目の値2と1番目の値1を取り出して、大きさを比較します。
値2と値1が等しいと1がスタックに積まれ、そうでなければ0が積まれます。


   使用例
     PUSHI 11
     PUSHI 22
     CMPE

スタックの上にPUSHI命令で積まれた値が比較され、一致しないので0がスタックに積まれます。

CMPNE

スタックの2番目の値2と1番目の値1を取り出して、大きさを比較します。
値2と値1が等しくないと1がスタックに積まれ、そうでなければ0が積まれます。


   使用例
     PUSHI 11
     PUSHI 22
     CMPNE

スタックの上にPUSHI命令で積まれた値が比較され、一致しないので1がスタックに積まれます。

CMPGT

スタックの2番目の値2と1番目の値1を取り出して、大きさを比較します。
(値2 > 値1)の場合1がスタックに積まれ、そうでなければ0が積まれます。


   使用例
     PUSHI 11
     PUSHI 22
     CMPGT

スタックの上にPUSHI命令で積まれた値が比較され、(11 > 22)なので0がスタックに積まれます。

CMPGE

スタックの2番目の値2と1番目の値1を取り出して、大きさを比較します。
(値2 >= 値1)の場合1がスタックに積まれ、そうでなければ0が積まれます。


   使用例
     PUSHI 11
     PUSHI 22
     CMPGE

スタックの上にPUSHI命令で積まれた値が比較され、(11 >= 22)なので0がスタックに積まれます。

CMPLT

スタックの2番目の値2と1番目の値1を取り出して、大きさを比較します。
(値2 < 値1)の場合1がスタックに積まれ、そうでなければ0が積まれます。


   使用例
     PUSHI 11
     PUSHI 22
     CMPLT

スタックの上にPUSHI命令で積まれた値が比較され、(11 < 22)なので1がスタックに積まれます。

CMPLE

スタックの2番目の値2と1番目の値1を取り出して、大きさを比較します。
(値2 <= 値1)の場合1がスタックに積まれ、そうでなければ0が積まれます。


   使用例
     PUSHI 11
     PUSHI 22
     CMPLE

スタックの上にPUSHI命令で積まれた値が比較され、(11 <= 22)なので1がスタックに積まれます。

AND

スタックの2番目の値2と1番目の値1を取り出して、両方とも1の場合はスタックに1を積みます。そうでなければ0を積みます。
CMP*命令の複合した結果の論理籍を取るのに使います。


   使用例
     PUSHI 11
     PUSHI 22
     DUP2
     CMPLE
     CMPNE
     AND


スタックの上にPUSHI命令で積まれた対の値がDUP2で複製され、(11 <= 22) AND (11 != 22)の結果の値1がスタックに積まれます。、

OR

スタックの2番目の値2と1番目の値1を取り出して、少なくとも片方が1の場合はスタックに1を積みます。そうでなければ0を積みます。
CMP*命令の複合した結果の論理和を取るのに使います。


   使用例
     PUSHI 11
     PUSHI 22
     DUP2
     CMPLE
     CMPE
     OR


スタックの上にPUSHI命令で積まれた対の値がDUP2で複製され、(11 <= 22) OR (11 == 22)の結果の値1がスタックに積まれます。、

NOT

スタックの1番目の値1を取り出して、0なら1プッシュします。そうでければ0をプッシュします。
CMP*命令の結果の判定を反転させる(not)に使います。


   使用例
     PUSHI 11
     PUSHI 22
     CMPLE
     NOT

スタックの上にPUSHI命令で積まれた対の値がDUP2で複製され、(11 <= 22) の結果を反転させた値0がスタックに積まれます。、

4.5.3 制御命令

BR オペランド

BR命令を実行すると、無条件にオペランドに指定されたアドレスにブランチ(分岐)します。
現在実行中のクロージャの中のアドレスだけが指定出来ます。他のクロージャのコードには飛べないことに注意してください。


   使用例
     BR 100

アドレス100番地に飛びます。

BRZ オペランド

BR命令を実行した場合、スタックの一番上の値を取り出します。
スタックから取り出した値が0の場合は、オペランドに指定されたアドレスにブランチ(分岐)します。
取り出した値が0でなければ、本命令はなにもせずに次の命令から実行されていきます。
BR命令と同様に、現在実行中のクロージャの中のアドレスだけが指定出来ます。他のクロージャのコードには飛べないことに注意してください。
BRZ命令はif文の条件判定の結果を処理するのに使います。


   使用例
     PUSHI 11
     PUSHI 22
     CMPE
     BRZ 100

値11と値22を比較して、一致しないため100番地に飛びます。

STOP

STOP命令を実行すると、CLVMはすべてのプログラム実行を終えて停止します。


   使用例
     STOP

プログラム実行が終了します。

4.5.4 call/return/save/restore命令

CALL

CALL命令を実行すると、スタックからクロージャ名を取り出し、そのクロージャのコードの0番地を呼び出します。
関数とクロージャは対応するので、CALL命令の実行は関数呼び出しに相当します。
飛んだクロージャ内でRET命令が実行されると呼び出し元のCALL命令の次のアドレスに戻ります。


   使用例
     PUSHI closure1
     CALL

closure1のコード領域の0番地を呼び出して実行します。

RET

CALL命令で呼び出されたクロージャ内でRET命令が実行されると、そのクロージャから元の呼び出し元のクロージャの呼び出しアドレスの次のアドレスに戻ります。


   使用例
     RET

RET命令の実行後に呼び出し元に戻ります。

SAVE

SAVE命令は、関数引数であるパラメターを対応する変数に設定します。
パラメターは、コンパイラにより、スタック上に(アドレス1, クロージャ1, 値1, アドレス2, クロージャ2, 値2, ...)のような形で設定されて来ます。パラメターは関数の引数と同じ数だけ設定されています。パラメター数は、4.2.1 オブジェクトとしてのクロージャで示した、クロージャ・オブジェクトの中のパラメター数を使います。
このような状態のスタックで、SAVE命令を呼び出すと以下のような処理が実行されます。


  1) スタックから最初の(アドレス, クロージャ, 新しい値)を取り出します。

  2) 取り出したアドレスとクロージャの指す元の値を取り出します。

  3) 取り出したアドレスとクロージャと元の値を、パラメター・スタックに保存します。

  4) 取り出したアドレスとクロージャの指す領域に新しい値を書き込みます。

  5) パラメター数だけ上記1)から4)を繰り返します。

この処理により、パラメター・スタックに保存された元の値は次に説明するRESTR命令を実行することにより元に戻されます。

RESTR

SAVE命令で保存された元の変数を、関数引数であるパラメター変数に戻します。
これは、関数の処理実行から復帰するときに実行される命令です。
RESTR命令は以下のような処理を実行します。


  1) パラメター・スタックから最初の(アドレス, クロージャ, 元の値)を取り出します。

  2) 取り出したアドレスとクロージャの指す領域に元の値を書き込みます。

  3) パラメター数だけ上記1)から2)を繰り返します。

4.5.5 動的メモリ拡張命令

BRK

BRK命令は、現在実行中のクロージャのデータ領域を拡張する命令です。
この命令は、実行されるとスタックの最初の値を取ってきます。
そして、この値の大きさまで、実行中のカレントのクロージャのデータ領域を拡張します。
このとき、すでにデータ領域が指定された値より大きく拡張されていた場合には、なにも実行せず次の命令に移ります。


   使用例
     PUSHI 20
     BRK

PUSHI命令でスタックにプッシュした値20まで、データ領域を拡張します。

CLR

現在実行中のクロージャのデータ領域をクリアします。
データ領域がゼロで初期化されるのではなく、まったく存在しなくなることに注意してください。
本命令実行後でも、BRK命令を使うと再度データ領域を拡張していくことが出来ます。


   使用例
     CLR

CLR命令でデータ領域をクリアします。

4.5.6 input/output命令

INPUT

INPUT命令を実行すると、入力待ちとなります。
数値、または文字列を入力すると、その値がスタックにプッシュされます。数値、または文字列は自動的に判定されて、対応する値がスタックにプッシュされます。
何も入力されないでEnterキーが押されると、空文字列""が、スタックにプッシュされます。


   使用例
     INPUT

INPUT命令では、入力した数値、または文字列がスタックにプッシュされます。

PR

PR命令は、スタックの一番上の値を取り出して、画面にプリントアウトします。
プリントアウトする値は、数値、文字列、クロージャのいずれでもOKです。出力時に自動的に判定されて適切に表示されます。
なお、PR命令では、出力後に改行されることがないことに注意してください。PR命令を連続して実行すると連続して繋がった形態で出力されます。


   使用例
     PUSHI 3.1415926
    PR
    PUSHI "I think about it"
    PR

PUSHI命令でスタック上にプッシュされた値が、画面にプリントアウトされます。

NL

NL命令は、実行されると改行します。PR命令と組み合わせて、文字列の画面出力に使われます。


   使用例
     PUSHI 3.1415926
    PR
    NL
    PUSHI "I think about it"
    PR
    NL

PUSHI命令でスタック上にプッシュされた値が、画面にプリントアウトされた後に改行されます。

4.5.7 その他

ADDSTR

スタックの2番目の文字列と1番目の文字列を取り出し、合わせた文字列をスタックにプッシュします。


   使用例
     PUSHI "STR1"
    PUSHI "STR2"
    ADDSTR
    PR

ADDSTR命令でスタック上にプッシュされた"STR1", "STR2"が合わされた"STR1STR2"が表示されます。

SUBSTR

スタックの3番目の値である文字列、2番目の値である位置、および1番目の値である長さを取り出して、文字列の中から、位置から長さの文字列を切り出して、スタックにプッシュします。


   使用例
     PUSHI "abcdefghijklmnopqrstuvwxyz"
    PUSHI 3
    PUSHI 3
    SUBSTR
    PR

文字列"abc..."から、3文字目から3文字の長さの"def"が取り出されます。

ISNUM

スタックの1番目の値を取り出し、数かどうか判定します。
数であれば、スタックに1をブッシュし、そうでなければ0をプッシュします。


   使用例
     PUSHI "abc"
     ISNUM
     PR
     PUSHI 3.14
     ISNUM
     PR

文字列"abc"は数でないので0が表示され、3.14は数なので1が表示されます。

RAND

スタックの一番上の値を取り出し、0から取り出した値より小さい範囲の整数の乱数をスタックにプッシュします。


   使用例
     PUSHI 10
     RAND
     PR

0からプッシュされた10より小さい範囲の整数の乱数が表示されます。

ERR

スタックの一番上の文字列値を取り出し、それを実行中のアドレスと合わせてエラーメッセージを作成して表示します。
表示メッセージは、以下のフォーマットで出力されます。


  error :[実行アドレス] メッセージ

メッセージの表示後にCLVM(バーチャル・マシン)は実行を終了して止まります。


   使用例
     PUSHI "system error occurs"
     ERR

エラーメッセージ"error:[23] system error occurs"が表示されて終了します。

4.6 発生するのイベント一覧

イベントは、VM内で命令を実行している場合に発生します。 以下にイベントの一覧を示しましょう。

RETURNおよびSTOPイベントは正常な動作の中で発生するイベントであり、その他のイベントは異常時に発生するものです。


EPARM        : CALL命令またはRAND命令で、引数の値が誤まっている場合に発生するイベント。

ERROR        : 不明なエラーの場合に発生するイベント。通常は発生せずVMの内部エラーで発生する。

ILLADDR      : メモリアクセス時に存在しないアドレスをアクセスすると発生するイベント。

ILLAREA      : 誤まった領域(コード領域またはデータ領域)へのアクセスで発生するイベント。

ILLCODE      : 存在しない命令コードを実行すると発生するイベント。

ILLCLOSURE   : 存在しないクロージャにアクセスすると発生するイベント。

NOTADDR      : アドレスが必要な命令で、数字以外の値が指定された場合に発生するイベント。

NOTANUM      : 浮動小数点数値が必要な場合にそれ以外の値が指定された場合に発生するイベント。

RETURN       : クロージャから復帰する場合に発生するイベント。エラーにはならないで呼び出し元のクロージャの処理に復帰する。

STOP         : STOP命令が実行されると発生するイベント。VMを正常終了させる。

USTKFLOW     : スタックがアンダーフローした場合に発生するイベント。