徒然なる日々を送るソフトウェアデベロッパーの記録(2)

技術上思ったことや感じたことを気ままに記録していくブログです。さくらから移設しました。

minosys script を作ろう (6)

Content クラスの全貌

前回、Content クラスを導入すると言いつつ、その内容については
断片的にしか記述しませんでしたので、ここで全体を俯瞰しておきます。

class Content {
 public:
  LexBase::LexTag tag;
  std::string op;
  std::string label;
  int inum;
  double dnum;
  std::vector<std::string> arg;
  std::vector<Content *>pc;

  Content *next, *last;

  Content() : tag(LexBase::LT_NULL), next(NULL), last(this) {}
  Content(LexBase::LexTag t, std::string o) : tag(t), op(o) {
    next = NULL;
    last = this;
  }
  Content(int itoken) : tag(LexBase::LT_INT), inum(itoken) {
    next = NULL;
    last = this;
  }
  Content(double dtoken) : tag(LexBase::LT_DNUM), dnum(dtoken) {
    next = NULL;
    last = this;
  }
  ~Content();
};
メンバー 説明
tag 中身の種類を示すメンバー変数
op 付加文字列を示す。LT_STRING ならリテラル文字列、LT_OPなら演算子名などとなる。
label 文にラベルがついている場合、そのラベル名
inum,dnum 定数が数値である場合、その値
arg 関数定義の場合、仮引数に使用された変数名
pc tagによって内容が異なる。後述
next,last 次の Content インスタンスまたは最後のインスタンスへのポインタ

arg メンバーは例えば

function test($a, $b) {
...
}

と定義されたとして、

arg = {"$a", "$b"}

と定義しておきます。実行エンジンでは content.tag が T_VAR である場合、
仮引数が指定されているかどうかチェックし、指定されていれば引数一覧から
その値を取得します。

この処理は例えば仮引数に LT_VARFUNC など仮引数専用のタグをつけて表現する
ことも可能です。仮引数の順番に番号をつけておくなどの工夫をすれば、
引数リストから簡単に目的の変数を索引できます。

今回の実装では関数呼び出し時に仮引数リストから引数辞書を作成しますので、
仮引数は LT_VAR のままとしました。

演算子木構造

minosys script では制御文でない文は全て評価式ですが、演算順序を表現
するため、木構造を導入します。

と言っても、演算子を頂点とし、その引数を pc メンバーに登録していく
だけの簡単なものです。

例えば

$b = $a + 3;

という評価式は以下のように変換されます。

b = new Content(LexBase::LT_VAR, "$b");
eq = new Content(LexBase::LT_OP, "=");
a = new Content(LexBase::LT_VAR, "$a");
plus = new Content(LexBase::LT_OP, "+");
c = new Content(LexBase::LT_INT, "");
c->inum = 3;

plus->pc.push_back(a);
plus->pc.push_back(c);
eq->push_back(b);
eq->push_back(plus);

eq がこの評価式の頂点として登録されます。

木構造は一般に右に行くほど深くなっていきますが、代入系演算子の左辺になる "." は
例外で、左に行くほど深くなっていきます。(こうなっている理由は実行エンジンの
実装に関係しますので、そこで触れます。)

関数の木表現

関数については特別なタグ LT_FUNC, LT_FUNCDEF を導入します。
両者の違いは(仮)引数の取り扱いです。関数定義では仮引数は引数の定義個所を
表現するのに対し、関数利用では引数は値として表現する必要があります。

LT_FUNCDEF では op に関数名を、arg に仮引数を、pc[0] に関数本体の定義を与えます。
LT_FUNC では pc[0] に関数名を、pc[1...] に引数を与えます。
従って実行時の関数名は演算によって動的に求められます。

配列の木表現

代入式の左辺値か、右辺値か、によって表現が異なります。
左辺値の場合は変数名の引数として定義されます。
従って、

func()[0] = ...;

のような表現は minosys script では構文解析エラーとなります。
右辺値の場合は、$a[0] であれば

a = new Content(LT_VAR, "$a");
z = new Content(LT_INT, "");
z->inum = 0;

k = new Content(LT_OP, "[");
k->pc.push_back(a);
k->pc.push_back(z);

のように表現されます。

次回は左辺値についてもう少し詳しく説明します。