課題 2


さて、引き続き簡易 PIC コンパイラを作りましょう。 今回の目標は、 です。 残念ながら、今回も局所変数の導入は見送ります(PIC 命令の説明や 関数フレームの説明に終始してしまうので)。

今回も、問題の意味が取りづらいところが多いでしょうが、 良くわからないところは早めに質問してください。 加筆していきますので。


今回は前回とは異なり、独力で作成してもらうところも増えています。 ということで、レポートの方は自分のプログラムの解説をするようにしてください。 単に、各関数の説明だけでなく、プログラムの概略や動作原理なども説明してください。

あと、scheme 演習も最後なので、感想をいろいろ書いてくれるとうれしいです。 来年度は、scheme 演習はなくなるんですが、他の演習などの参考にもしたいですし。 それから、2000年度アンケートにも答えておいてください。

オプション課題に関しては、余力のある人だけが解いてくれればOKです。 といっても、それほど難しくないので、多くの人に解いて欲しいところです。 また、課題提出にあたっては、オプション課題を解くつもりの人も、 他の課題が出来た時点で提出してもらって、 オプション課題は追加提出(revised)の形で提出してくれてOKです(基本的に 早めの提出も評価項目に加えているので)。



まず、前回関数呼び出しの機構を作っていなかったので、作ってみましょう。 今回の簡易言語のプログラム文面上では、関数は
(def-func 関数名  文)
という形をとることにします。複数の文を書きたい人は、 文のところを複文(begin ...) にしましょう。

さて、プログラムは def-func 文が複数並んだリストで与えられるものとします。 つまり、こんな感じ。

((def-func main
   (begin (if x  (:= y 2) (return))
          (call func1)
          (...))

 (def-func func1
   (begin (while y 
             (begin (:= y (- y 1))
                    ....))
          (....)))

 (def-func foo
   (begin (....)
          (....))))
ということで、各 def-func 文を変換する trans-def-func と、 リスト状の複数のdef-func文に対して変換を行う trans-def-funcs があれば、プログラムを変換可能という事になります。
つまり、コンパイルする際は、まず trans-def-func(s) を呼び出し、 その中で trans-stmt(s) が利用され、さらに trans-expr(s) が 使われることになるわけです。

trans-def-funcが行うのは、以下のような変換です。さあ、それぞれ作ってみましょう。

(trans-def-func '(def-func func0 stmt))
=>
(func0          ; function を示す label
 .....          ; 以後、stmt を変換したもの
 .....           
 (RETURN))

(trans-def-funcs '((def-func func0 stmt0) (def-func func1 stmt1)))
=>
(func0          ; func0 のlabel
 .....          ; 以後、stmt0 を変換したもの
 .....           
 (RETURN)
 func1          ; func1 の label
 .....          ; 以後、stmt1 を変換したもの
 .....           
 (RETURN))
これだけでは、関数呼び出し出来ないので、call 文 (call 関数名) と RETURN 文 (return) を付け加えましょう。 trans-stmt を拡張して付け加えましょう。それぞれ、以下の様に変換されます。
(trans-stmt '(call func0)) => ((CALL func0))
(trans-stmt '(return)) => ((RETURN))

ここは、完全に余談ですが、PIC をつかっていると、port をたたいたりする場合、 アセンブリ言語を使いたいときもあります。そんなために asm 命令を付け加えました。

;;; (trans-stmt '(asm (BCF PORTB 0))) => ((BCF PORTB 0))
;;; とできるように すこし拡張

(define (trans-stmt stmt)
   (cond ....
         ((eq? (car stmt) 'asm)
          (trans-asm-stmt (cdr stmt)))
         ...))

(define (trans-asm-stmt stmt) (list (car stmt)))

つぎに、for 文を導入しましょう。ここでは、以下のようなsyntax をとることにします。
;;; (for (初期化文 条件式 ステップ文) 本体の文)
(for ((:= i 0)        ; 初期化に利用したい文
      (- 10 i)        ; ループを繰り返すための条件式(0以外はtrue、0はfalse) 
                      ;  不等号は導入してなかったので、変更しました。以下、同様
      (:= i (+ i 1))) ; 各ステップ毎に実行する文
  (:= result (+ result i)))  ; for 文の body 部の文

for 文を作るのに、いきなり機械語レベルで考えても良いのですが、 ここでは少し違った方法を取りましょう。for 文はなくても、while 文はもう作ってあります。 以下の、while 文を含んだ複文は、先ほどの for 文と同じ役割をするはずです。

(begin
  (:= i 0) 
  (while (- 10 i)
     (begin
        (:= result (+ result i))
        (:= i (+ i 1)))))
ということで、先ほどの例のように for 文から while文を含んだ複文に変換するexpand-for-stmt という関数を作ってみましょう。
(expand-for-stmt 
  '(for ((:= i 0) (- 10 i) (:= i (+ i 1)))
      (:= result (+ result i))))
=>
(begin
  (:= i 0) 
  (while (- 10 i)
     (begin
        (:= result (+ result i))
        (:= i (+ i 1)))))
で、この関数を利用すれば、for 文は、
       (trans-stmt (expand-for-stmt stmt))
とすればアセンブリ言語にまで変換出来るはずです。

実は、このように構文の幾つかは、文の形を変換すれば等価なもので代用出来るものがあります。 こういった構文は、文の変換規則をあたえてやるだけで、実は出来てしまうんです。 scheme をはじめとした Lisp の処理系では、マクロといわれる機能をつかって、 一般のプログラマが新たな構文を作って、登録することが出来るようになっています。


で、せっかく作ったわけですから、 簡単なプログラムを書いて、 うまく変換されているのかどうか試してください。 プログラム例と変換結果は、レポートにのせるようにしてください。

こちらからのサンプルとして、 プログラム例変換例をのせておきます。 こちらは、レポートにのせなくてもOKです。


さて、最後は、オプション課題です。余力のある人はどうぞ。 ヒントも少なめです。

入れ子状になった式を処理してみましょう。 このままでは処理ができないので、一時変数として大域変数 tmp0, tmp1, ... を利用することとします。

(+ (- (- x y) 2 (+ x y)) ; PIC で積演算を導入してないので、*- 演算に訂正します
   (- (- x z) 3 (+ x z))
   (+ y z)
   5)
という式があった場合、
(:= tmp0 (- x y))          ; 第 0 式の中の評価 tmp0, tmp1 を順次利用        
(:= tmp1 (+ x y))
(:= tmp0 (- tmp0 2 tmp1))  ; 結果については、第 0 式は tmp0 に格納
(:= tmp1 (- x z))          ; 第 1 式の評価には、tmp0 が埋まっているので、tmp1 以降を利用
(:= tmp2 (+ x z))          
(:= tmp1 (- tmp1 3 tmp2))  ; 結果については、第 1 式は tmp1 に格納
(:= tmp2 (+ y z))          ; 第 2 式の評価には、tmp0,tmp1 が埋まっているので、tmp2 以降を利用
という文を処理した後、
(+ tmp0 tmp1 tmp2 5)
を実行すると考えてもあまり問題はありません。 ということで、入れ子状の式については、まず上のような文と式に変換を施した後、アセンブリ言語 に変換してみましょう(やり方はいろいろなので、別の方法で実装しても構わないです。でも、一時変数を大量消費はしないように)。

ただし、 n 番目の一時変数(tmp0, tmp1, ...) は、(gen-tmp-var n) で作成できることとする。

(define (gen-tmp-var n)
  (let* ((str-n (number->string n))
         (str-var (string-append "tmp" str-n)))
    (string->symbol str-var)))

一応、入力プログラム例と、 変換例を示しておきます。

加えて、ヒント(誘導)をつけてみました。


2001.01.10/ Tomio KAMADA: kamada@cs.kobe-u.ac.jp