Isabelle Tutorial その8

3.3 Case Study: Compiling Expressions

今節では、変数、定数、そして二項演算子からなる式からスタックマシーンへのコンパイラを開発します。これは、2.5.6のブール式を一般化したものです。変数や値の型は特に定めず、型パラメータとして定義します。二項演算子も詳細は定めず、適当な演算子を持っているとします。

types 'v binop = "'v => 'v => 'v"
datatype ('a, 'v) expr = Cex 'v
                       | Vex 'a
                       | Bex "'v binop" "('a, 'v) expr" "('a, 'v) expr"

三つの構成子は定数、変数、二項演算子の適用を表わします。式をある環境の下で評価する関数は次のように定義できます。

primrec "value" :: "('a, 'v) expr => ('a => 'v) => 'v" where
"value (Cex v) env       = v" |
"value (Vex a) env       = env a" |
"value (Bex f e1 e2) env = f (value e1 env) (value e2 env)"

スタックマシーンは、定数をスタックに積む、アドレスが指す値をスタックに積む、スタックの上位二つの値を演算子に適用し結果をスタックに積む、という三つの命令があります。exprと同じように、アドレスと値の型は型パラメータとします。

datatype ('a, 'v) instr = Const 'v
                        | Load 'a
                        | Apply "'v binop"

スタックマシーンの実行は関数execでモデル化します。これは命令のリスト、記憶領域(式の評価の時の環境と同じように、アドレスから値への関数でモデル化します)、そして実行前のスタックを引数に取り、実行後のスタックを返します。

primrec exec :: "('a, 'v) instr list => ('a => 'v) => 'v list => 'v list" where
"exec [] s vs     = vs" |
"exec (i#is) s vs =
  (case i of Const v => exec is s (v#vs)
           | Load a  => exec is s ((s a)#vs)
           | Apply f => exec is s ((f (hd vs) (hd (tl vs)))#(tl (tl vs))))"

関数は全て全域的なので、空リストに対するhdも定義されており、結果は不定です。よってexecも実行が終了することは保証されていますが、例えば空のスタックに対してApplyが実行されたときなど、結果が不定となる場合があります。

コンパイラは式から命令列への関数です。次のように定義できます。

primrec compile :: "('a, 'v) expr => ('a, 'v) instr list" where
"compile (Cex v)       = [Const v]" |
"compile (Vex a)       = [Load a]" |
"compile (Bex f e1 e2) = (compile e2) @ (compile e1) @ [Apply f]"

このコンパイラの正当性を証明します。これは、式の評価結果と、コンパイル後の命令列の実行結果が同じになることで確かめます。

theorem "exec (compile e) s [] = [value e s]"

ヒューリスティクスに従って一般化します。

theorem "ALL vs. exec (compile e) s vs = (value e s) # vs"

eに対する帰納法で証明しようとすると、二つの命令列の結合に関する補題が必要になることがわかります。

lemma exec_app[simp]: "ALL vs. exec (xs@ys) s vs = exec ys s (exec xs s vs)"

これを証明しようとすると、命令に対する二つのcase式が残ります。よってcase式に対する場合分けルールを追加して証明します。

apply(induct_tac xs, simp, simp split: instr.split)

または次のメソッドでも証明できます。

apply(induct_tac xs, simp_all split: instr.split)

この補題により、一般化したコンパイラの正当性も証明できます。