スタート SML# 2 に行ってきた
スタート SML# の「スタート」ってなんだろう、ということを非常に考えさせられるイベントでした。
こじつけるとするならば・・・
- 言語のスタートってなんだろう・・・?
- やはり処理系のビルドではないだろうか・・・?
- よし、SML# の処理系を弄ろう!
と言ったところでしょうか。
成果としては、cop という新しいキーワードを追加することに成功しました。
コードは多分後で公開します。
ネタ
ネタはいくつか持っていきました。
- cop キーワードの追加*1
- Python 的な、比較演算子のチェイン*2
- fn のカリー化対応*3
- 配列リテラルの追加*4
- 'a -> string な関数または printf の追加*5
- REPL の応答の改良*6
- C との連携の強化*7
C との連携の強化は難しそうだけど、
Cの構造体をSML# のレコードとしてインポートできる1文が欲しい!C関数が構造体へのポインタをリターンする,とかを簡単にインポートしたいね. #smlsharp
という方向性はよさそうですね。ただ、時間内に成果が出るとはとても思えないので却下。
'a -> string や printf や REPL の応答は、コード追ってるだけで時間切れする感が半端ないので却下。
配列リテラルは (個人的には) そこまで欲しくないので、
- cop の追加
- 比較演算子のチェイン
- fn のカリー化対応
の 3 つに絞り、最終的には cop の追加をやりました。
以下その記録のようなもの。
準備
で、その前にまず開発環境を整える必要があったので、前半はその作業してました。
なぜって、現行の SML# さん、MinGW でビルドが成功しないのです・・・
なので、仕方なく Ubuntu 12.04 を入れ(11 時ごろ。本会開始は 14 時)、
Git の環境を整え(ソースからビルド)、
Vim の環境を整え(これはさすがに aptitude で)、
ようやっと SML# のビルドが終わったのが 15時 46分。
SML# 自体は --enable-fast-build オプションの力ですんなりビルドできました。
cop の追加
cop の追加の方針ですが、今回は cop の構文木として既存の構文のシンタックスシュガーという形で実装しました。
cop f
とあったら、これを
fn _x => fn _y => _x f _y
に相当する構文木を返すようにします。
基本的には src/compiler/parser/main/iml.grm の編集と、それに伴い必要となる修正だけでできました。
一番重要な部分は iml.lex と iml.grm の 2つのファイルへの追加です。
iml.lex
このファイルは、字句解析のルールを記述したファイルで、「cop」という文字列を「T.COP」というトークンに変換するというルールを記述します。
<INITIAL>"op" => (T.OP (left(yypos,arg),right(yypos,2,arg)));
というのがあったので、これの下に
<INITIAL>"cop" => (T.COP (left(yypos,arg),right(yypos,2,arg)));
を追加しました。
これで、smllex にこのファイルを食わせると iml.lex.sml というファイルができるのですが、.smi ファイルは作ってくれないらしいです。
なので、iml.lex.smi ファイルにも修正が必要が出した。
iml.grm
このファイルは、構文解析のルールを記述したファイルです。
COP を終端記号として扱うように記述するのと、COP をどのような構文木に変換するのかを記述する必要があります。
終端記号として扱うようにするのは、
%term EOF ... | COMMA | COP ...
と、COMMA の後に COP を追加しただけです。
構文木への変換はちょっと面倒です。
atexp_noVAR : constant (Absyn.EXPCONSTANT(constant,(constantleft,constantright))) | OP expid (Absyn.EXPOPID(expid,(OPleft,expidright))) (* ここを追加 *) | COP expid (Absyn.EXPFN( [(Absyn.PATID {id = ["_x"], loc = Loc.noloc, opPrefix = false}, Absyn.EXPFN( [(Absyn.PATID {id = ["_y"], loc = Loc.noloc, opPrefix = false}, Absyn.EXPAPP([ Absyn.EXPID(["_x"], Loc.noloc), Absyn.EXPID(expid, (expidleft, expidright)), Absyn.EXPID(["_y"], Loc.noloc) ], Loc.noloc) )], (COPleft,expidright) ) )], (COPleft,expidright))) ...
Absyn.なんたら、というのを組み合わせて構文木を作るわけです。
このあたりは、src/compiler/parser/main/ABSYN.sig や src/compiler/parser/main/Absyn.ppg を参考にしました。
ppg の方を見れば必要な情報は全部載っているのですが、sig の方には要約された感じで載っているため、主に sig の方を見ながら進めました。
これも smlyacc に食わせるわけですが、こちらも smi ファイルは生成されないため、自分で修正しました。
interface.grm
SML# では、字句解析を smi ファイルと sml ファイルで共有しているそうです。
なので、interface.grm にも少々手を入れる必要がありました。
が、まぁここは取るに足りない部分です。
コンパイル!
で、コンパイルするわけですが・・・
iml.grm.sml のコンパイルにめちゃくちゃ時間がかかりますね・・・
一回目は Absyn.EXPID を勘違いしており、smlsharp は起動するものの cop がうまく動きませんでした。
二回目以降は細かいミスや型エラーで何回かビルドに失敗。
ご飯を食べるための移動中もノート PC 開いて移動してました。完全に怪しい人ですね。
で、ご飯食べ終わっても全然ビルドが終わらず、電源もやばくなってきたので解散。
家に帰ってきてからビルド再開して、やっとビルド終了しました。
cop を使ってみると・・・
copできた! #startsmlsharp URL
foldl (op +) 0 (List.tabulate (100, (cop +)1)); #startsmlsharp URL
やりました!
感想
よんたさん処理系の内部に詳しくて素敵!
よんたさんのおかげで SML# に独自キーワード追加できるようになりました!!
SML# (の処理系弄り)、スタートできました!!!
*1:標準の op キーワードはタプル形式で関数を返してくるため、部分適用できないので、カリー化形式で関数を返す cop キーワードを追加する。
*2:0 < x <= 10 とか書くと、0 < x andalso x <= 10 みたいに解釈される(ただし、x は 1回しか評価されない)。
*3:なぜか fn x y => x + y とか出来ないので、fn x => fn y => x + y とかしなくちゃいけなくてだるいので fn x y => x + y と書けるようにする。
*4:配列リテラルがないので、追加する。ただ、Vector とかどうするんだ、という問題はある。
*5:任意の型を pretty print したい。
*6:SOME 10 とか評価させると、val it = _ : int option と残念な結果になるので、val it = SOME 10 : int option と表示されるようにする。
*7:C の構造体へのポインタを返すような関数との連携がびみょいので、どうにかする。