プログラミング言語の作り方のサンプルとして、言語「samplan」を作った

「samplan」という新しいプログラミング言語を作りました。

ソースはGitHubに上げてあります。

github.com

私のWebページの方に、説明記事を上げておきました。

kmaebashi.com

ざっくりとした見た目はこんな感じになります。

# samplanサンプルプログラム
var hoge int := 10;
var piyo real := 20.0;
var result real := ((hoge + piyo * 2) / 3 - 5);

print("result.." + result);

# 1から、与えられた引数までの数値の和を計算する関数
function sum(value int) int {
  var i int := 1;
  var sum int := 0;

  while (i <= value) {
    sum := sum + i;
    ++i;
  }
  return sum;
}

# 1~10の和は、55
print("sum(10).." + sum(10));

発端

元はみずしまさんが「今から一時間でプログラミング言語を一つ作る」というのをやっておられたのを見て、まあ私には1時間では無理ですが、言語をひとつガシガシと作ってみたくなったのでした。

そこで、10/23土曜日の昼過ぎから作成開始。

正直、うまくいったらこの日のうちに何となくでも動いて、動いたらビールで乾杯、くらいに思っていたのですが、さすがに処理系いっこ、静的型付けありでVMで動く言語となるとそうはいかず、土曜はパーサが動くので精一杯、日曜にまたちょっと作業して、その後の平日もちまちま(1時間かそこらしか時間は取れないのですが)作業したりしなかったりして、金曜の晩に一応最後まで書いて動かしてみたがめちゃくちゃ、次の土曜は晴れたのでいつもの養老コースで走りに行って、夕方に帰宅後ちょっと直してようやくぼちぼち動くようになったのでした。

samplanはどんな言語か

samplan(sample languageの略です)は、CやJavaJavaScriptのような波括弧系の言語ですが、Cとかとは以下の点が異なります。

  • if文とかの{ }は省略できません。その代わりにifの後ろの()は不要です。
  • if文の{ }が省略できないので、Cとかのelse if構文は使えません*1。その代わりに予約語elsifを追加しています。
  • 型は後置です。つまり「int hoge;」ではなくて「var hoge int;」です。思えばCの、「型 変数名;」であるかのように見える(Cでは実は違うのですが)という宣言の構文にどれほど多くのプログラマが惑わされてきたことか!!
  • 代入は=ではなく:=です(Pascal風)。それに伴い、等値比較は「=」になっています。

かつて私は以下のページでcrowbarとかDiksamといった言語を作りましたが、

kmaebashi.com

この時は、if文の{ }は省略不可にしてelsifを導入したものの、型は前置にしましたし、代入は=にしました。if文の( )については、crowbarの最初のバージョンでは不要にしたものの、自分自身間違えてばかりだったので結局戻しました。Cに慣れたプログラマ(私)にとって、Cに近いということはそれなりにメリットがあるだろう、と考えたわけですが、まあ、samplanは、実用的に使おうなどという人が現れるはずもないので好き勝手してもいいかな、と。

実質3人日程度で作った簡易言語なので、配列もありません。作っておいて言うのも何ですが、まあ現状では実用性はありません。でもその分処理系は小さいので、プログラミング言語の作り方を知りたい、という人が眺めるにはよいのではないでしょうか。

実装について

samplanは、レキシカルアナライザやパーサのジェネレータ(Cならlex/yaccJavaならJavaCCとか)を一切使わず、手書きのレキシカルアナライザと、1トークン先読みの再帰下降パーサで構文解析を行っています。

上記のcrowbarやDiksamではlex(flex)とyacc(bison)を使いましたが、構文解析周りは、知らない人からすれば、黒魔術に見えるほど難しい作業に見えると思います。その黒魔術部分を、ブラックボックスのパーサジェネレータに頼ったのでは、魔術を解明したという面白みに欠けるでしょう。再帰下降パーサでプログラミング言語を作る、というのは、私が表に発表したものでいうと、11年以上前に日経ソフトウエアの特集記事に載せていただいたスクリプト言語MILがそれにあたります。

kmaebashi.hatenablog.comただ、MILは、全ソースコードを紙面に乗せようとしたためソースの行数に制約があり、いろいろ試した結果、「解析木を作らずに直接VMのコードを吐く」という形になりました。解析木を作るとなると解析木のための型定義が必要で、VM用のバイトコードを吐かない解析木実行タイプの言語にしたとしても、型定義の分だけで長くなってしまうようです。この時は、解析木を作るものも含め何パターンか実際に処理系を作ってみて、一番処理系のソースが短いものとしてこれを選んだ記憶があります。関数定義も(行数の都合で)入れられなくて、代わりにgosubを付けていますね。

行数が少ない方が読者にはとっつきがよいでしょうし、挫折する人も減るかもしれませんが、「解析木を作らずに直接VMのコードを吐く」言語は、入門書ならともかく、実際にはあまりないでしょう。言語の作り方を学ぶなら、解析木を作るところもやっておきたいところです。また、crowbarや昔のRubyのような「解析木を辿りながら実行する言語」は、最初のうちは確かに楽に作れるのですが、breakやcontinueや中途returnを入れたり、(Cで作るなら)GCのことまで考えると、結構面倒くさくなります。それなら、最初からVMを作った方が手っ取り早いと思います。

そんなわけで、samplanは、コンパイラが生成したバイトコード*2をSamplan Virtual Machine(SVM)が実行するタイプの言語としました。上であげたDiksamもバイトコード実行タイプの言語なので、samplanのコンパイラ構文解析から先はDiksamをJavaに移植したようなものですし、SVMの命令セットはほぼDVM(Diksam Virtual Machine)のサブセットです。samplanのソースを読んでみようという人は、上記「プログラミング言語を作る」のDiksamの解説が役に立つかと思います。

作ってみて

この1週間ばかり、寝食を忘れて、というほどではないですが、帰宅したらそそくさとPCの電源を入れてコードを書く、という生活でした。こういうのは久しぶりです。洗濯機を回したら即コード書きに戻って、気づいたら洗剤入れるの忘れてた、とかね。

俺もまだこんな風にコード書きに熱中できるんだなあ、という感慨はありますが、それが、何度も書いたことがあり過去コードを貼り合わせたような言語処理系、というのはちょっと残念ですね。もちろん過去何度も書いたようなコードでも新しい発見はあるものですが、次は何か、新しいものを作ってみよう……

それはそれとして

ちょっと前ですが、「完全初心者のためのプログラミング入門」にも新章追加してます。こちらもよろしくお願いします。

kmaebashi.com

広告

宣伝しても、今となっては中古本しか手に入らないでしょうから、私の稼ぎにはなりませんが…… (Kindle化って重要)

*1:Cとかにはelse if~という構文がある、と思っている人は割と見かけますが、あれは単に{}を省略したelse節に次のif文がぶらさがっているだけです。

*2:型はintなので「バイト」コードではないですが