Commits

camlspotter committed e26a948

added the file

Comments (0)

Files changed (1)

+===============================
+CamlP4 も悲観的入門
+===============================
+
+肝に命じること
+=======================
+
+CamlP4 はオーパーツもしくはロストテクノロジー。全部を理解しようとしてソース本体を読み始めると魂を取られ帰ってこれなくなる。真髄は理解できないものとして、便利に使う、使えなさそうならさっさと諦める、という姿勢が重要。
+
+OCaml の文法拡張フレームワークとしてのP4
+============================================
+
+P4 は OCaml の文法を拡張することが出来るだけでなく、独自言語のパーサーやプリティプリンタを記述することもできる。できるが時間の無駄だからやめろ。Lex(OCamlLex/ULex)+Yacc(Menhir) があるじゃないか。
+
+というわけで OCaml の文法を拡張する道具としての P4 についてしか述べない。
+
+どう動作するのか
+=======================================
+
+* P4 は改造構文を含む OCaml のソースコードをパースルールを使用してパースする
+* パースしながら改造構文の無い vanilla OCaml(以下 バニラ)のソースコードツリー(以下 AST)を生成する
+* コードをパース終了した後にも AST を変形させることができる (Filter)
+* 最終的にバニラコードを出力する
+
+君の仕事は
+
+* P4 が提供するバニラのパースルールを基に
+* 文法拡張のためのパースルールを追加し
+* 文法拡張部分や Filter のための AST 生成器を書き
+* それを P4 ランタイムに登録する
+
+ことになる
+
+Original Syntax, Revised Syntax なんと複雑な!
+=================================================
+
+P4 にはバニラ OCaml の文法である Original syntax とは別に Revised syntax という別の OCaml 文法が実装されており、これが P4 のオーパーツ化の始まりとなっている。注意して欲しいのは
+
+Revised syntax は君の OCaml プログラミングを revise する物ではない
+
+ということだ。実際のところ Revised syntax は Original syntax と比べて人間の目には冗長に見える。Original syntax を知っている人には違いを覚えるのも大変である。
+
+Revised syntax が有利になるのは P4 での拡張を Quotation を使って書く場合だけである。言語の枠構造などが人間に優しく省略されている Original syntax では、 Quote を使って拡張を書く場合境界がはっきりせずうまく書けない場合がある。Revised は P4 のための DSL として作られており、そのような境界が明示されているので書きやすい。
+
+では Revised syntax を習得するべきか。否。Revised syntax は無視してよろしい。というか無視しろ。この文法は P4 でしか役に立たないので覚えるのは時間の無駄である。Original syntax では Quote が書けなくなるじゃないですか…という人には、Quote を使わなくても P4 は書ける、と答えよう。Revised syntax でうんうん唸るなら書けない、書きにくい Quote が現れたらそこはベタに AST コンストラクタを書く(生書き)。もちろん Quote が書けてそちらの方が簡便な場合は Quote を使おう。それが近道だ。
+
+camlp4, camlp4o, camlp4of, camlp4oof, camlp4orf, camlp4r,  camlp4rf … ナメてんのか!
+-----------------------------------------------------------------------------------
+
+Quote の外と中の言語を上記の Syntax のどちらで書くか、そして reflective であるかどうか(言語拡張が Quote 内部にも適用されるべきか)の違いにより、 P4 には 2x2x2 = 8 種類のバリアントが想定される、そのため P4 には沢山のコマンドがある。君が使うべきはまず一つ、
+
+camlp4of
+
+である。Quote の外と中両方共 Original syntax で reflective なものだ。それ以外は忘れろ。
+
+演習
+-------------------------------
+
+* 100行程度の x.ml という OCaml バニラソースを用意せよ。無ければ書け。
+* camlp4of x.ml を実行して出力を確認せよ。
+* camlp4 x.ml を実行して出力を確認せよ。エラーが出た場合、それは何故か考えよ。
+* [重要] camlp4of x.ml > xx.ml を実行し xx.ml を確認せよ。
+* [重要] camlp4of -printer Camlp4OCamlPrinter x.ml > xxx.ml を実行し xxx.ml を確認せよ。
+
+解説: P4 は OCaml コンパイラのプリプロセッサとして動作させることが多い。P4 と ocamlc の間でのソースのやり取りはわざわざ人間に読める OCaml コードを出力する意義は無いのでバイナリで行われる。 出力先がターミナル以外の場合、プリンタを明示しないと P4 がバイナリを吐くのはそのためである。
+
+もう複雑ではなくなったね!
+=============================================
+
+Original syntax 一本! コマンドは常に camlp4of! そう決めたらかなり見通しが良くなったはずだ。
+次に進もう。
+
+
+できないことを知ろう
+===============================================
+
+P4 で出来ないこと。夢想しても逆立ちしても出来ないこと。
+
+* Lexer を変更してオレオレリテラル書きたい! => 諦めろ。  えっでも書けるようなドキュメントが => 諦めろ。pa_xxx.cmo を積み重ねる方式の P4 での文法拡張では常にバニラLexerが使用される
+* 型チェックした後の情報から… => 諦めろ。P4 は型付け前、パース段階で AST をいじるフレームワークなので、型付け情報は扱えない。でも… => じゃあ P4 内部で自分で型推論器実装してみればなんとかなるはずなので勝手にやってくれ
+
+文法拡張のテンプレート
+=============================
+
+なぜかは聞かず、このテンプレを使う::
+
+    open Camlp4
+    
+    module Id : Sig.Id = struct
+      let name = "pa_XXX"  (* change *)
+      let version = "1.0"  (* change *)
+    end
+    
+    module Make (Syntax : Sig.Camlp4Syntax) = struct
+      open Sig
+      include Syntax
+      open Ast
+    
+      (* 文法拡張部 *)
+
+    end
+    
+    let module M = Register.OCamlSyntaxExtension(Id)(Make) in ()
+
+* Id は拡張の名前とかバージョンとかを書く。ありがたかったことがない
+* Make という functor は OCaml シンタックスパーサーモジュール Syntax : Sig.Camlp4Syntax
+  をもらってそのモジュールを基に同じ型(Sig.Camlp4Syntax)のモジュールを生成して返す。
+  この functor を積み重ねる方式により、複数の文法拡張を同時に使用することができる。
+  当然、変な変更を積み上げるとまともに使えない文法になるが気にしてはいけない。
+* 文法拡張部はここでは触れない
+* 最後に Register.OCamlSyntaxExtension を使ってこの Make を登録する。結果のモジュール M に特に使い道はない。
+
+演習問題
+------------------
+
+* [重要] 上のテンプレを camlp4temp.ml に保存し ocamlc でコンパイルせよ。コマンドは ocamlc -pp camlp4of -I `ocamlc -where`/camlp4 -c camlp4temp.ml
+* [重要] 上記コンパイルコマンドを一々打ち込まなくても良いよう、自分の使用しているビルドツールのルールを作成せよ。
+
+Quotation と Anti-quotation
+===================================
+
+さて、テンプレに触れたから早速文法拡張に移りたいところだが…その前に Quotation system を見なければならない。少し落ち着け。Quote system とは OCaml 内部で OCaml の syntax tree (AST) を OCaml ソースの形で記述できるようにするための言語内 DSL だと思って良い。AST を簡単にいじるために必須なツールだ。
+
+Quote
+--------------------------
+
+P4 では Quasi-quotation (Quote) が使える。Quote を使えば、言語 AST の内部表現を書く(生書きとでも呼ぼう)代わりに、より人間様に判りやすい言語ソースをそのまま書くことができる。
+
+例えば、P4 での空リスト [] 式の内部表現は::
+
+    Ast.ExId (_loc, (Ast.IdUid (_loc, "[]")))
+
+であるが、Quote を使えば::
+
+    <:expr<[]>>
+
+と書くことができる。空リストパターンの内部表現は::
+
+    Ast.PaId (_loc, (Ast.IdUid (_loc, "[]")))
+
+であるが、Quote を使えば::
+
+    <:patt<[]>>
+
+で済む。残念ながら Quote 内部のソースコード片をパースさせるコンテクスト(expr, patt などは)は明示しなければならない。
+
+演習問題
+-----------------
+* let _ = <:expr<[]>> というファイルを作り、 camlp4of で出力して、 Quote が内部で何に展開されているか確認せよ。
+* let _ = <<[]>> というファイルを作り、 camlp4of で出力して、出力を確認せよ。結果は役に立つのでメモしておくこと。使える expander のリストが手に入った!
+
+Quote が展開される AST の定義
+-------------------------------
+
+さて、Quote が AST 内部表現に展開される例を見たが、そこで出てくる Ast.ExId やら Ast.IdUid はどこで定義されているか。どのようなコンストラクタがあるか。もっとも簡単な資料は OCaml ソースコードディレクトリ($OCAML と略記)の $OCAML/camlp4/Camlp4/Camlp4Ast.partial.ml である。これは Revised syntax で記述されており、なおかつこのファイル自体が P4 が作成される際にコンパイルされるわけではないのだが、もっとも判りやすい。ここに定義された型名は Quote <:XXX< ... >> のコンテクスト名 XXX として使用できる。
+
+Revised syntax でのバリアント定義の読み方だが例えば、::
+
+    | StExt of loc and string and ctyp and meta_list string
+
+であれば、Original syntax の ::
+
+    | StExt of (loc,   string,    ctyp,    string meta_list)
+
+に相当する。読み替えはそれほど難しくはないはずだ。
+
+Camlp4Ast の各コンストラクタは一応コメントされているもののその使用方法はよくわからないことが多い。例えば、::
+
+    and ctyp =
+      [ ...
+      | TyApp of loc and ctyp and ctyp (* t t *) (* list 'a *)
+      ...
+ 
+これはどうやら引数を持つデータ型の適用のためのコンストラクタである(実際そうだ)。コメントも Revised syntax で書かれているので list 'a とは 'a list のことである。さて、TyApp は二つの ctyp を取るが、'a list の場合どちらが 'a でどちらが list か。('a, 'b) Hashtbl.t の場合は ('a, 'b) をどうエンコードするのか。云々。Camlp4Ast には時にドキュメントされていないインバリアントがあり、Ast として型のあった式を作成しても P4 のバニラ出力時に拒否されてしまうことがある。
+
+どうしたらよいか。実例を見てインバリアントを確かめるのが最も良い。次の演習をやりなさい。
+
+* <:ctyp< 'a list >>
+* <:ctyp< ('a, 'b) Hashtbl.t >>
+* <:ctyp< int list option >>
+* [重要] これらを camlp4of で展開してどのような AST ツリーになるか確認せよ
+
+_loc とは「例の場所」
+-------------------------
+
+Quote 展開例でしばしば見られる自由変数 _loc は式の場所を指す。この自由変数はもっと外のパターンで Quote を使っている限り、自動的に束縛されることになっているので Quote を使っている限りは気にすることはない。ただし、 Quote を使わず生書きする場合は少し注意する必要がある。
+
+Quote では _loc を書く必要は無いが、明示的書きたい場合があるその場合は::
+
+    <:patt@myloc<[]>>
+
+の様に書く。 
+
+演習問題
+------------------------
+* <:patt@myloc<[]>> の quote 展開を確認せよ
+
+
+テンプレ内部での Quote
+-----------------------------
+
+Quote の展開例で見たように、 quote 展開では Camlp4Ast.partial.ml に記述されたコンストラクタが Ast. を付けて使用される。(例えば Ast.PaId) これを P4 文法拡張で使用する際には、テンプレの「文法拡張部」で Ast という名前のモジュールにアクセスできるようになっていなければならない。
+
+実際には functor パラメータ Syntax に Ast モジュールがある (すなわち Syntax.Ast)。この Syntax.Ast を Ast としてアクセスするためには Syntax を open するか include する必要がある。実際の P4 文法拡張においては Syntax モジュールを変更し新しい Syntax を創りだす場合が多いので include Syntax を見ることが多い。
+
+演習問題
+--------------------
+* テンプレコードの「文法拡張部」に let _ = <:expr<[]>> と書いてコンパイルを試みよ
+* なぜ失敗するか、 camlp4of で quote 展開結果を確認せよ
+* 自由変数 _loc をλ抽象で適当になんとかして再度コンパイルを試みよ
+
+Anti-quotation
+--------------------
+
+Anti-quotation(Anti-quote) は Quotation の中に外部の値を導入するための Quote の中の Quote。
+書式は $ 式 $ と書く。$ は OCaml では普通に使える symbol character なのだが、camlp4of では
+$ が Anti-quote のために予約されているため $ は使えなくなってしまう。なので $ を OCaml で
+使うのは避けよとは言わないが、注意しておくべし。
+
+Anti-quotation には(おそらく)全くドキュメントされていない使用方法、$XXX: 式$ がある。
+これは Anti-quote を書きやすくするための syntax sugar で(おそらく)次のものがある::
+
+    let _ = <:expr< $x$ >>              (* 普通。x がそのまま使われる *)
+    let _ = <:expr< $id:x$ >>           (* x : ident を expr にする *)
+    let _ = <:expr< $lid:x$ >>          (* x : string を IdLid の ident expr にする *)
+    let _ = <:expr< $uid:x$ >>          (* x : string を IdUid の ident expr にする *)
+    let _ = <:expr< $str:x$ >>          (* x : string を 文字列 expr にする *)
+    let _ = <:expr< $int:x$ >>          (* x : string を 整数 expr にする *)
+    let _ = <:expr< $int32:x$ >>        (* 略 *)
+    let _ = <:expr< $int64:x$ >>        (* 略 *)
+    let _ = <:expr< $nativeint:x$ >>    (* 略 *)
+    let _ = <:expr< $flo:x$ >>          (* x : string を 浮動小数点 expr にする *)
+    let _ = <:expr< $chr:x$ >>          (* x : string を 文字 expr にする *)
+
+$XXX: 式$ 書式を使えば、例えば、 <:expr< $ ExId (IdLid (_loc, x)) $ >> 
+と本来なら書くべきところを <:expr< $lid: x$ >> と短く済ませることができる。
+
+$XXX: 式$ の XXX 部分は Camlp4Ast のコンストラクタ名 ExXXX から来ている。
+$XXX: 式$ は <:expr< ... >> だけでなく <:patt< ... >> などでも使用可能。
+
+演習問題
+------------------
+* [重要] 上記コードを camlp4of で展開し(ry
+* [重要] 上記コードの expr を patt に変えて camlp4of で(ry
+
+Quotation system まとめ
+-------------------------
+
+* Quote が使いづらいと思ったら Camlp4Ast のコンストラクタを生書きする
+* Quote や Anti-quote の展開がわからなかったら例題を作って camlp4of で実際にどうなるか確かめる
+
+---
+* 補助コード部分では AST をいじるのに必要な関数を定義する
+* EXTEND Gram から END までは文法拡張のための DSL ゾーン。
+  ここでは基になる文法モジュール Syntax 内の P4 パースルール群を変更、拡張する。