MachiKania

MachiKania BASICをオブジェクト指向にする計画

2018年9月25日

現在、MachiKania BASICで、クラスオブジェクトを作成して使用する為のコードを模索している。色々ややこしい実装になると思われるので、実装方法についてメモを公表することにした。この記事は、完成までに何度も編集される可能性が高い。また、この企画はボツになる可能性もある。
KM-BASICでオブジェクトの作成を可能にする。別ソースコードを読み込んで使用することも、目的の一つである。クラス名とメソッド名・フィールド名は、英数字6文字までとし、32ビット整数値で扱えるようにする。

追加される機能
  • USECLASS ステートメント:ソースコードの始めに記載する。クラスファイルを取り込み、クラスを使用可能とする。
  • NEW() 関数:新規オブジェクトを作成する。引数はクラス名、つづけてコンストラクターの引数。
  • foo.x : オブジェクトのフィールドへのアクセス。
  • foo.c(): オブジェクトのメソッド呼び出し。
  • CLASS ステートメント:クラス宣言
  • FIELD ステートメント:public 及び private フィールド宣言
  • METHOD ステートメント:メソッド宣言。INITメソッドは、コンストラクター。

使用例

USECLASS ACLASS
o=NEW(ACLASS)
o.field1=123
print o.TEST("test")

ACLASS.BASの例

CLASS ACLASS
FIELD public field1
METHOD INIT
  print "An ACLASS object is constructed."
  return
METHOD TEXT
  print "Argument is ";ARGS$(1);". return field1+1"
  return field1+1


全体の大まかな仕様について

cmpdata

クラス名と、クラス情報格納領域へのポインターを保存する。


コンパイル

各クラスファイルは、他のファイルとは別にコンパイル・リンクする。
  • この仕様により、別ファイルのラベルにジャンプするコードになったりしてしまうことを防ぐ。
  • 長い名前の変数については、ファイルごとに別々の変数番号を割り当てるようにしなければならない。
  • METHODが在る場合、その開始アドレスを保存しておく。
  • クラスファイル内におけるグローバル変数的記述を、クラス内でのスタティック変数とするか、グローバル変数とするかは、考え所。グローバル変数が使用出来る環境はバグの温床になりがちなので、たぶんクラス内のスタティック変数で行くことになる。

コンパイル・リンク終了後、次の動作が必要。
  • クラス名の保存。
  • フィールド名を格納した配列。クラス情報とひも付けしなければならない。
  • 各々のメソッドの開始アドレスを格納した配列。クラス情報とひも付けしなければならない。


リンク

リンク時には、各オブジェクトがどのクラスのインスタンスであるかの情報がない。従って、コンパイル時やリンク時にフィールド名やメソッド名によりクラス情報を解決して実行ファイルを作成することは不可能である。


ライブラリー

オブジェクト操作用のコードをより高速に実行するため、一部の機能では、BASICのライブラリー機能は使用しない。変わりに、Cコードによる関数を直接呼び出す形にする。switch文を経由しないため、かなり高速になるはず。この実装にする機能は、以下の通り

  • オブジェクトのフィールド名による、参照先の解決
  • オブジェクトのメソッド名による、呼び出しアドレスの解決


オブジェクト情報構造体

この構造体は、NEW()呼び出しの際に構築される。構造は以下の通り。
  • クラス情報格納領域へのポインター
  • フィールドの変数値格納。複数在る場合は続けて格納。始めにpublic、続けてprivate。



クラス情報構造体

この構造体は、CLASSファイルのコンパイルが終了した後、その直後の領域に構築される。構造は以下の通り。
  • クラス名(32ビット整数)。
  • フィールド数/メソッド数。bit 0-7: public fields, bit 8-15: private fields, bit 16-23: public methods bit 24-31: reserved。
  • フィールド名(32ビット整数)。始めにpublic、続けてprivate。
  • メソッド名(32ビット整数)。publicのみ格納。


フィールドへのアクセス

フィールドの指定は、変数名の後に「.」(ピリオド)が続き、続けてフィールド名が来て、「(」(カッコ)が無い場合である。

コンパイルの際は、オブジェクトがどのクラスのインスタンスであるかの情報がない。従って、フィールドデーターの格納位置の解決は、プログラム実行時に行なわれる。この目的のために、ライブラリーとは別に関数が用意される(高速処理するため)。この関数にオブジェクトのポインターとフィールド名を引数として与えれば、フィールド格納アドレスが返される。

メソッド呼び出し

メソッドの指定は、変数名の後に「.」(ピリオド)が続き、続けてメソッド名が来て、「(」(カッコ)が在る場合である。ジャンプ先は、リンカーに委ねる。後は、GOSUBと同じように扱う。ただし、オブジェクト格納アドレスをスタックに保存する形でMETHODステートメントに引き継がなければならない。

コンパイルの際は、オブジェクトがどのクラスのインスタンスであるかの情報がない。従って、メソッド開始アドレスの解決は、プログラム実行時に行なわれる。この目的のために、ライブラリーとは別に関数が用意される(高速処理するため) 。この関数にオブジェクトのポインターと関数名を引数として与えれば、フィールド格納アドレスが返される。

各機能をどの様にコンパイルするか

USECLASS ステートメント

USECLASSステートメントは、ソースコードの冒頭に記述しなければならない。コンパイラーがこの記述に到達すると、
  1. cmpdata_find()により、指定されたクラス名がすでに登録されているかどうかを調べる。
  2. 登録されていればスキップし、次のクラス名を調べる。全てのクラス名が登録されている場合、操作を終了する。
  3. クラス名が登録されていない場合:
  4. cmpdata_insert()を呼び出し、クラス名を登録する。
  5. 現在コンパイル中のファイルのファイル名をスタックに保存する。
  6. "クラス名.BAS"ファイルを開き、その内容をコンパイルする。
  7. 初めからからやり直す。ただし、ソースコードファイルは1から読み込み直す。


NEW() 関数

第一引数は、クラス名である。cmpdataにより、クラスが存在するかどうかを調べる。無ければ、エラー。第二引数以降は、コンストラクターに引き渡される値。

コンパイラーがこの記述に到達すると、以下の動作を行なうコードを作成する。
  1. 引数格納用のスタックを作成する。
  2. 引数を順次格納する。
  3. クラス情報へのポインターを$a0に格納する。
  4. 直前のlet_statementにより使用された変数番号を、$a1に格納する。
  5. NEW用のライブラリーを呼び出す。

NEW用のライブラリーの動作は、以下の通り。
  1. オブジェクト格納領域を、変数番号により指定された変数に確保する。
  2. INITメソッドが在れば、呼び出す。この時、保存されているスタック領域へのポインターを$s5に代入する。
  3. 最後に、格納領域のアドレスを$v0に代入する。


フィールド

  1. "i=get_var_number()"により、iに変数番号が入っている。
  2. "."を認識。フィールドもしくはへのアクセスである。
  3. gel_label()呼び出し。g_labelにラベル番号(32ビット整数)を得る。
  4. "("が認識されない。フィールドへのアクセスである。
  5. "sw a0,xxx(s8)"(ただし、xxxは(i*4) )により、$a0にオブジェクトへのポインターを得るコードを作成。
  6. $a1にg_labelの値を代入するコードを作成。
  7. C関数呼び出しコードを作成。$v0に、オブジェクトのフィールドへのポインターを得る。

代入の場合
  1. $v0をスタックに格納。
  2. 次のソースコードが"#"の場合実数値を、その他の場合整数値を$v0に得るコードを作成。
    • ここで「"$"の場合文字列を」としたい所だが、実装が難しい。とりあえず、フィールドには文字列は指定出来ない仕様で行く。
  3. スタックから$v1を待避。
  4. $v1(0)に$v0を代入。

値取得の場合
  1. $v0に$v0(0)を代入。

C関数
  1. 第一引数はオブジェクト情報へのポインター、第二引数はフィールド名(32ビット整数)。
  2. オブジェクト情報[0]から、クラス情報へのポインターを得る。
  3. クラス情報[1]から、publicフィールド数を得る。
  4. 第二引数と同じフィールド名を、publicフィールドリストから得る。見つからなければエラー。
  5. オブジェクト情報へのポインターに(publicフィールド番号 x 4)を加算し、$v0として返す。

メソッド

  1. "i=get_var_number()"により、iに変数番号が入っている。
  2. "."を認識。フィールドもしくはメソッドへのアクセスである。
  3. gel_label()呼び出し。g_labelにラベル番号(32ビット整数)を得る。
  4. "("を認識。メソッドへのアクセスである。
  5. スタックを作成。引数を順次スタックに格納。
  6. スタック+4を、$s5に代入。
  7. "sw a0,xxx(s8)"(ただし、xxxは(i*4) )により、$a0にオブジェクトへのポインターを得るコードを作成。
  8. $a1にg_labelの値を代入するコードを作成。
  9. C関数呼び出しコードを作成。$v0に、オブジェクトのメソッドへのポインターを得る。
  10. $v0にGOSUBするルーチンを作成。GOSUBでdynamicに行番号を呼び出すのと同じコードである。

C関数
  1. 第一引数はオブジェクト情報へのポインター、第二引数はメソッド名(32ビット整数)。
  2. オブジェクト情報[0]から、クラス情報へのポインターを得る。
  3. クラス情報[1]から、public/privateフィールド数とメソッド数を得る。
  4. 第二引数と同じメソッド名を、メソッドリストから得る。見つからなければエラー。
  5. オブジェクト情報へのポインターに(メソッド番号 x 4)を加算し、$v0として返す。


CLASS ステートメント

「CLASS クラス名」。このステートメントは、クラス定義ファイルの冒頭に記述するべきである。ただし、REMはそれより前に記述出来る。クラス名は、ファイル名と合致しなければならない。そうでなければ、シンタックスエラー。


FIELD ステートメント

「FIELD PUBLIC」もしくは、「FIELD PRIVATE」。ただし、「PUBLIC」は省略可能。このステートメントは、CLASS ステートメントに続けてすぐに記述するべきである。ただし、REMはそれより前に記述出来る。

基本的にUSEVARと同じ動作を行なう。ただし、クラス情報構造体構築のため、レコードを取っておかなければならない。


METHOD ステートメント

FIELD ステートメントにより定義された変数へのアクセスに関しては、特殊なコーディングが必要になる。メソッドが呼び出された時に、変数のそれぞれの値をオブジェクト構造体から読み込み直し、RETURNする前にオブジェクト構造体に保存し直すようにすればよいのではないか?この動作は、METHOD ステートメントの部分で行なえばよいと思われる。

METHOD ステートメントの部分では、次の動作を行なう。VARステートメントの実装を参考にする。

  1. スタックを準備
  2. 全てのフィールド(PUBLIC/PRIVATE両方)の変数値を、スタックに待避。
  3. 全てのフィールド(PUBLIC/PRIVATE両方)の変数値を、オブジェクト要素値に置き換える。
  4. 8.をコールする。RETURNにより、5.に戻る。
  5. 全てのフィールド(PUBLIC/PRIVATE両方)の変数値を、オブジェクト要素値に保存する。
  6. 全てのフィールド(PUBLIC/PRIVATE両方)の変数値を、スタックから呼び出す。
  7. RETURN
  8. 次のステートメント


クラス情報構造体

クラスファイルのコンパイルが終了した後に、クラス情報構造体を構築する。この構造体は、public/privateフィールド数、メソッド数及び、各フィールドとメソッドのリストからなる。

コメント

コメントはありません

コメント送信