インタプリタ式の言語処理系を書いたことが無かったので一度実装してみようと思って，この手のは Lisp が定番だということで，前々から気になっていた Make a Lisp (mal) に挑戦してみました．

Make a Lisp (mal) とは

Make a Lisp は色々な言語で mal という Lisp 方言を実装してみようというプロジェクトです．

30以上の言語での mal 処理系実装

11段階のステップに分けられた実装ガイド（全体の構成図付き）

各実装ステップごとのテストケース

といったほしい情報が揃っており，言語処理系初心者でも Lisp 実装について簡単に学べる環境が整っています． 11段階の各ステップは以下の様な感じです．

ちょうど Crystal という言語が以前から気になっていたので，せっかくなので Crystal で実装することにしました．

Crystal

Crystal は Ruby の文法をベースにした静的型付き言語です（なので名前も宝石から取っています）．見かけはかなり Ruby に近いですが，コンパイラが型チェックを行い型が合わない場合はコンパイルエラーに落としてくれますし，バイナリの実行もかなり速いです．元々動的型付き言語の Ruby を静的型付き言語に持ってくるために色々な工夫をしています．

arr = [ 1 , 2 , 3 , " foo " ]

例えば上のような Array の要素の型は Crystal では Int32 | String という Union type になります． Union type は実際にどの型が入っているかを確認した後でないと使えません．

elem = arr.sample elem + 10 puts elem.to_s case elem when Int32 elem + 10 when String elem + " ! " end

この他の様々な箇所でもコンパイラが型情報をかき集めてきて，取り得る型をすべて合わせた Union type として推論します．よって，Crystal ではごく一部の例外を除いてほとんど型を書く必要がありません（型を書かないのが良いかどうかは別の問題ですが）．

ただ，異なる点もかなりあるので，Ruby だと思って書くとうまくいかないことがあります．

デフォルトが nilable でない（型 T の変数に nil を入れたければ T | Nil 型を使う）

の変数に を入れたければ 型を使う） String が変更不可能

が変更不可能 eval が無い

eval が無いというのは（Ruby に比べて）かなり自由度が低くなるように見えますが，Crystal にはマクロがあるためコンパイル時にメソッドを定義したり Ruby の method_missing みたいな処理もコンパイル時であれば可能です．また，デバッグに便利な p メソッドなどもマクロで実装されています．

ただ，まだ pre-alpha ステージで現在進行形で開発中のため，コンパイラのバグがあったり，仕様変更があったりします．どれくらい開発中かというと，Crystal で mal を実装しているとメイン開発者の Ary さんがカジュアルに commit コメントで「Crystal の使用感どう？」と聞いてくるぐらいです．なお，すでにセルフホスト済みで，Crystal のコンパイラは Crystal で書かれています．

Crystal による Lisp (mal) 実装

Crystal は以前からちょっと気になっていたので，Make a Lisp は Crystal で実装しました．

Lisp の値のデータ構造は Union type を使って簡潔に書くことができ，他の箇所も（Ruby でいう Proc は若干曲者でしたが）さくっと書くことができました．（もちろん拙い箇所もたくさんあったり，コンパイラのバグを回避するためにワークアラウンドした箇所もありますが…）

ガイド は若干情報が足りていない（組み込み関数の仕様とか）ことがあったり若干内容が難しいことがありますが，そういうときは他の言語での実装や失敗したテストケースを確認することで理解することができます．自然言語で説明されるよりも実際のコードで見るほうが分かりやすいことも多いです．また，他の知らない言語での実装を見ながら実装を進めるのも楽しいです．

僕の場合は Nim での実装を読みながら参考にしたり，どうしても分からない箇所はよく知っている Ruby での実装を読んで理解したりしました．

最後のステップまで実装しきった時の実装量は全体で 2500 行ぐらいで，各ステップでファイルを取っておくので，実際の実装は 1000 行程度です．難易度的には最後のセルフホスト時にセルフホストした側でバグが見つかると特定が少し大変ですが，それ以外はほぼ苦戦しませんでした．なお，コミットログを見ていると，速い人だと3日ほどで実装しているようです（e.g. Nim）．1日1時間1ステップずつ実装していくというのも良さそうです．

Crystal で実装する上で見つけたコンパイラのバグは Crystal のリポジトリの issue に報告したり，気づいた箇所を修正して pull request したりでいくらか貢献することができました．また，Crystal での mal 実装はまだなく，せっかくなので mal に pull request を出して取り込んでもらいました．

また，せっかくなので今後もちょくちょくいじっていきたいと思い単独のリポジトリ化しました．

https://github.com/rhysd/Crisp

まとめ

基本的なインタプリタ型の動的型付きの言語処理系の中身がどうなっているのか勉強になりましたし，Lisp の強力かつ動的な機能で色々遊ぶことができました． Lisp はそもそもどういう言語かもよく知らなかったのですが，テストケースが充実していたり他の言語での実装があったのでなるほどなぁという感じで進められました．

まだ mal のリポジトリに無い言語で実装すると自分の実装を取り入れてもらえてどことなくお得感があります．例えばD言語とか，最近話題(?)の Elixir とか Vim script とか，まだ mal 実装が無いようです．

ただ，Lisp （のいち方言）の処理系を実装してみましたものの，相変わらず何故か Lisp 自体は書ける気がしません…