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

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

minosys script を作ろう (5)

lex の追加

大事なことを忘れていました。C 言語などでは改行コードを \n のように
表現できますが、minosys script でも同じことができるようにします。
ただし、\n, \r, \\ の3つのみをサポートすることにしました。

構文解析

構文解析では lex によって切り出されたトークンの並びを調べ、
実行エンジンに都合のよいデータ構造(構文解析木)に変換します。

なので、どのようなデータ構造が処理しやすいか考えてみます。

まずデータ構造を表現するためのクラス Content を導入します。
Content が何を表しているのか表現するため、tag メンバーを
追加します。あとは数値ならば inum, dnum, 文字列なら op、
引数を必要とする構造(関数や配列)なら pc などをメンバーに
追加していきます。

単純な文の連続

制御構造も何もない関数定義ですが、これは文が現れる都度
文を評価していけばよいので、単方向リストとして用意するのが
よさそうです。関数の最後に到達したら next は null とします。

次に制御構造別に考えてみます。

label

label は制御構造ではないですが、後述の break, continue でループ制御
するときに使用されます。
単なる文字列なので、文を表すデータ構造に string 型で持っておくのが
よさそうです。

複文

複文開始記号 { で始まる文は1つの引数を持つ LT_BEGIN 型の関数として
定義します。engine の動作を先取りしますが、実行中プログラムポインタは
コールスタックに退避され、第1引数で定義される文から実行を開始します。
最後まで(next が NULL になるまで)実行したら、コールスタックから
実行中プログラムポインタを取得し、次の文を実行します。

ここのところは通常の CPU の動作と異なるため、engine の実装に
触れるところで解説します。

if 文

if 文は3つの引数を持つ LT_IF 型の関数として定義します。
第1引数は評価式、第2引数は評価式が真だった場合に実行される文、
第3引数はもしあれば、評価式が偽だった場合に実行される文となります。

for 文

for 文は4つの引数を持つ LT_FOR 型の関数として定義します。
第1引数はループ当初に1回だけ評価される評価式、第2引数はループに入る都度
評価される評価式、第3引数は第2引数を評価する直前に評価される評価式、
第4引数は第2引数が真だった場合に実行される文となります。

while 文

while 文は2つの引数を持つ LT_WHILE 型の関数として定義します。
第1引数はループの最初で評価される式、第2引数は評価式が真だった場合に
実行される文となります。

break 文

break を効率的に実行するため、ラベルハッシュを導入し、構文解析中に構成
しておきます。ラベルハッシュは定義関数の中におけるラベルの表現位置と
複文によるネストを表現するもので、以下のように定義されます。

std::unordered_map<std::string, std::unordered_map<std::string, Label> > labels;
class Label {
 public:
  int nest;
  Content *content;
};

continue 文

continue 文は break 文同様ですが、コールスタックから復帰した時の動作が異なります。

その他の評価式

その他は制御構造を持たない通常の演算子、または変数、定数として扱われます。
演算子は LT_OP 型、変数は LT_VAR 型、定数は LT_INT, LT_DNUM, LT_STRING となります。
関数呼び出しは特別な型 LT_FUNC を、関数定義は LT_FUNCDEF を使用します。
このほかのタグは関数名を構成することになりますが、LT_TAG として残しておきます。

次回は実行可能な、評価式の木構造による表現方法について考えてみます。