Rubyで素朴な自作言語のコンパイラを作った



RubyでオレオレVMとアセンブラとコード生成器を2週間で作ってライフゲームを動かした話 の続きです。 このときスコープ外にしていたパーサ部分(高水準言語から構文木への変換部分)をやっと書きました。 3日くらいでできたのでさっさとやっておけばよかった。

パーサまで揃っていなかったため仕方なく「コード生成器を作った」という表現にしていましたが、 ここまでやったら「コンパイラを作った」と言っていいはず!



  • ふつうの手書き再帰下降パーサ
    • 400行ちょっと
  • 難しいことはしない
    • 再帰下降パーサを実際に書くのは今回が初めてなので
    • まずはハードルを下げて「とにかく動く」とこまで持って行く
      • 下げ過ぎた気がしなくもない
    • 構文木を割とそのままマッピングしていて、気の利いた変換とかはやってない
    • 式の優先順位は考慮しない
      • 明示的に括弧を書く
      • 気が向いたら後でやる
  • 汎用ではない
  • ベタな文法でよい
    • 「今までにない画期的な言語を作るぞ!」みたいな野望はない
  • エラーハンドリングは適当
  • ふつうの文法でふつうの再帰下降パーサなので、あまり書くことがないです……

文法サンプル

見ての通り、特にひねりのない感じです。 パッと見では JavaScript に近いでしょうか。

set, call, call_set は明示的に書きます。

// コメント

// 関数定義
func add(a, b) {
  // return(返り値は省略可)
  return a + b;
}

func main() {
  // ローカル変数宣言
  var a;

  // ローカル変数宣言+初期化
  var b = 1;

  // ローカル変数への代入
  set a = 2;

  // 関数呼び出し
  call add(1, 2);

  // 関数呼び出して返り値をローカル変数に代入
  call_set c = add(1, 2);

  // while
  while (a != 10) {
    // ...
  }

  // case
  case {
    (a == 1) {
      // ...
    }
    (a == 2) {
      // ... 
    }
    // ...
  }

  // VMコメント
  _cmt("...");
}

実行

run.sh を修正しました。 これまでと似た感じで、 ./run.sh gol.vg.txt で パース→コード生成→アセンブルVMで実行 が一度に実行されます。

参考

2020-06-25 追記

節目っぽいのでこの時点での行数を数えてみました。 空行やコメントだけの行も含めた単純な行数です。

   14 common.rb
   63 vgasm.rb
  474 vgcg.rb
  433 vgparser.rb
  491 vgvm.rb
 1475 合計

思ったより少ない。2,000行超えてるかなと思ってましたが。

gol.vg.txt と、コンパイルで生成される vgtコード・アセンブリコード・機械語コードも行数を見てみます。

  208 gol.vg.txt
  874 tmp/gol.vgt.json
  693 tmp/gol.vga.txt
 1299 tmp/gol.vge.yaml

フーン、という感じですね(気の利いた感想が出てこなかった)。

その後の変更

けっこういいかげんなところがあるので、後から修正しています。

関連