スタート SML# 2 に行ってきた

PARTAKE

スタート SML# の「スタート」ってなんだろう、ということを非常に考えさせられるイベントでした。
こじつけるとするならば・・・

  • 言語のスタートってなんだろう・・・?
  • やはり処理系のビルドではないだろうか・・・?
  • よし、SML# の処理系を弄ろう!

と言ったところでしょうか。
成果としては、cop という新しいキーワードを追加することに成功しました。
コードは多分後で公開します。

ネタ

ネタはいくつか持っていきました。

  1. cop キーワードの追加*1
  2. Python 的な、比較演算子のチェイン*2
  3. fn のカリー化対応*3
  4. 配列リテラルの追加*4
  5. 'a -> string な関数または printf の追加*5
  6. REPL の応答の改良*6
  7. C との連携の強化*7

C との連携の強化は難しそうだけど、


という方向性はよさそうですね。ただ、時間内に成果が出るとはとても思えないので却下。
'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 を使ってみると・・・


やりました!

感想

よんたさん処理系の内部に詳しくて素敵!
よんたさんのおかげで 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 の構造体へのポインタを返すような関数との連携がびみょいので、どうにかする。