言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

自己記述 lisp コンパイラ Tamacola のインタラクティブシェル完成。

前回コンパイラが完成したと書いてから 2 ヶ月程経ち http://d.hatena.ne.jp/propella/20100507/p1 ようやく実用的なインタラクティブシェルが完成しました。これで Tamacola も普通の Scheme として使えます。こんなに時間がかかったのは、マクロを正しく実装するのに手間がかかったからでした。マクロが入ると、コンパイラの中でコンパイラ再帰的に呼び出す場合が生まれるので、今までグローバル変数使いまくりのやっつけで作っていたコンパイラではうまく行きませんでした。これから一ヶ月書けてドキュメントを書いて、出来たらコードを公開したいと思っています。しかし、簡単に解決出来る問題をすべて解決してしまって行き詰まっている状態なので、頭を整理するためにこの日記を書いています。

まず出来上がっていない機能について纏めます。そもそもプロジェクトの目的は、新しいプログラミング言語をウェブ上で作る事が出来るという事なので、ウェブ上でちゃんと動かす必要があります。最初は今までのように WebDAV でストレージを作って、あとでちゃんとしたウェブサービスにしたいです。言語を作るという部分は、OMeta/JS のように PEG 文法を Web フォームから入力してパーサを作成という事になると思います。その場合 Lisp 文と PEG を混ぜて書ける文法を作るか、モードみたいな物を準備する必要があります。

PEG 自体の実装もまだ迷っています。今は PEG の非終端記号(?) を一つの関数にコンパイルしているんだけど、ネームスペースが無いので色んな文法を作ると関数名がかぶってしまいます。ネームスペースを作るか、OMeta のように文法をオブジェクトと、ルールをメソッドとして表現した方がいいです。

迷っているのは、本当に文法をオブジェクトとして表現出来るかという所です。オブジェクトの利点は、オブジェクト自体がネームスペースになる所と、文脈状態やパース結果の置き場所として使える所です。また OMeta では継承関係を使って他の文法のルールを参照する事も出来ますが、これは問題じゃないかと思っています。

例えば、空白にマッチとか、数字にマッチとか、よく使うルールをライブラリとして他の文法から参照したい時の扱いです。他の文法にあるルールを利用したい状況はそもそも is-a 関係では無いので、ここを継承と考えるのは概念的におかしいです(例えば、「空白にマッチしたく無い」文法を作るのに「空白にマッチする」文法を継承するのは変です)。特に単一継承とはそりが合わない。

また、ある文法のルールが他の文法から参照される事を考えると、文法と状態オブジェクトとの関係は一対一では無いので、ルールをメソッドと考えるのも変です。そこで、やっぱりネームスペースはネームスペースとしてちゃんと用意した方が良いかも。。。と思っています。

では AVM のネームスペース機能を使おうとしたら、実装上のさらなる問題が浮上しました。それは修飾しない名前を参照する時、同時に二つのネームスペースの中にあるとエラーになるという点です。つまり、ネームスペース間に優先順位を付けられないのです。

例えば標準で使われている関数を上書きしたい事を考えます。標準で、spaces という関数が用意されていて、自分の目的のためには spaces を変更したいんだけど他の部分はそのまま使いたい時があるでしょう。そこで別のネームペースに別の spaces を定義しても、名前が衝突してしまうので使う時には完全修飾しないといけません。これではネームスペースの利点がありません。もしもコンパイル時にネームスペース内のシンボルが全て分かっていたら、コンパイル時にどちらのネームスペースを使うか決定出来るので、最悪この手でしのげるかもしれません。

他には、ネームスペースをオブジェクトで代用しようかというアイデアがあります。

オブジェクトはネームスペースと違って名前の検索の優先順位を持たせる事が出来ます。具体的には prototype を使うか with を使う事で実現出来ます。これはコンパイル時にやる事が少ないので簡単で良いです。問題は、ActionScript と違う言語の構成要素を増やすのは筋が悪そうという事でした。

しかし、Clojure のネームスペースの文法を調べて、ちょっと勇気づけられました。Clojure では、クラス変数のアクセスとネームスペースのアクセスに同じ文法を使います。例えば、Class/method, Namespace/variable のように使います。一瞬これはもしやネームスペースをクラスとして実装しているのではとびっくりしたのですが、結構水面下で色々努力してこういう事になってるみたいです。概念的には、クラスメンバもネームスペース変数も似たような物なので気持ちは分かります。しかも、AVM は Java と違って動的にメンバが足せるのでずっと素直に実装出来ます。

という事で、色々考えて、ネームスペースはオブジェクトで実装する。データ構造はオブジェクトで実装する。しかし、データ構造は直接関数と結び付かない。という感じに落ち着きそうです。

とはいえ、また考えが変わるかもしれないので、一ヶ月ドキュメント作成と、どうでも良い部分の実装に専念して、ネームスペース問題は先送りにしたいと思っています。他の実装上の問題は次の通りです。

まず、コンパイル速度が非常に遅いです。言語処理系全体をコンパイルするのに二分もかかります。二分?速い!と思いたい所ですが、普通 Scheme の処理系は数万行のソースコードテキストを実行時に読んでその場でコンパイルしてすぐ実行する物だと思うので、tamacola のように数千行に二分かかるのは恥ずかしいです(実行はまあまあ速いです)。

あと、DSL を多用しているというのもこのプロジェクトの売りなんですけど、その割にパーサーとコンパイラと、この二つにしか使ってないので、アセンブラにも使いたいです。

最近ウェブ上でプログラムが書けるというのを色々見かけて、すごい期待しています。

こういうのを見ると、技術的な点もさておき、コミュニティ作りというのは重要だなと思います。全くそう言うのに目を向けず、ウェブ上でコンパイラの作成というのはかなり斜め上な気もするけど、楽しくやっています。