3imp 読んでみる (7)

なかなか stack based に辿りつきません。

コンパイラの前に

record-case の定義の検討を

(define-syntax record-case
  (syntax-rules (else)
    ((_ exp1 (else exp3 ...)) (begin exp3 ...))
    ((_ exp1 (key vars exp2 ...)) 
     (if (eq? (car exp1) key) (record vars (cdr exp1) exp2 ...)))
    ((_ exp1 (key vars exp2 ...) c ...)
     (if (eq? (car exp1) key) 
	 (record vars (cdr exp1) exp2 ...) 
	 (record-case exp1 c ...)))))

なんか Kent Dybvig さんのナニをパクリまくってます。これも含め、試験書いた方が良さげ。とりあえずソースを準備。TDD な原則として微妙ですが以下。

;; heapbased.scm
(define-syntax rec
  (syntax-rules ()
    ((_ a b) (let ([a '()])
	       (set! a b)))))

(define-syntax recur
  (syntax-rules ()
    ((_ f ([v i] ...) e ...)
     ((rec f (lambda (v ...) e ...)) 
      i ...))))

(define-syntax record
  (syntax-rules ()
    ((_ (var ...) val exp ...)
     (apply (lambda (var ...) exp ...) val))))

(define-syntax record-case
  (syntax-rules (else)
    ((_ exp1 (else exp3 ...)) (begin exp3 ...))
    ((_ exp1 (key vars exp2 ...)) 
     (if (eq? (car exp1) key) (record vars (cdr exp1) exp2 ...)))
    ((_ exp1 (key vars exp2 ...) c ...)
     (if (eq? (car exp1) key) 
	 (record vars (cdr exp1) exp2 ...) 
	 (record-case exp1 c ...)))))

(define (tail? next)
  (eq? (car next) 'return))

(define (compile x next)
  (cond
   [(symbol? x)
    (list 'refer x next)]
   [(pair? x)
    (record-case x 
		 [quote (obj)
			(list 'constant obj next)]
		 [lambda (vars body)
		   (list 'close vars (compile body '(return)) next)]
		 [if (test then else)
		     (let ([thenc (compile then next)]
			   [elsec (compile else next)])
		       (compile test (list 'test thenc elsec)))]
		 [set! (var x)
		       (compile x (list 'assign var next))]
		 [call/cc (x)
			  (let ([c (list 'conti
					 (list 'argument
					       (compile x '(apply))))])
			    (if (tail? next)
				c
				(list 'frame next c)))]
		 [else
		  (recur loop ([args (cdr x)]
			       [c (compile (car x) '(apply))])
			 (if (null? args)
			     (if (tail? next)
				 c
				 (list 'frame next c))
			     (loop (cdr args)
				   (compile (car args)
					    (list 'argument c)))))])]
   [else
    (list 'constant x next)]))

上記がビンゴかダウトかは知りませんが試験を。とりあえず rec から、なんですが使い方としては

(rec var exp)

の exp は lambda 式がデフォルト、で試験して良いのでしょうか。例示されているナニとしては以下で

(rec count
     (lambda (x)
       (if (null? x)
	   0
	   (+ (count (cdr x)) 1))))

な式が戻すのは closure だけど count は中でのみ有効な名前になっているはず。あと、戻りな closure の試験を含め、以下

;; test-rec.scm
(use gauche.test)

(add-load-path ".")
(load "heapbased")

(test-start "rec")

(test-section "3imp sample")
(let ((p (rec count
	      (lambda (x)
		(if (null? x)
		    0
		    (+ (count (cdr x)) 1))))))
  (test* "nonexistent count" *test-error* count)
  (test* "rec return closure" 5 (p '(1 2 3 4 5)))
  )

(test-end)

Makefile 使ってれば emacs で全部完結するのに今日ようやく気づきました。Makefile は微妙ですが以下

SHELL=/bin/sh

TARGET=$(wildcard test-*.scm)

test:
	@rm -f test.log
	@for X in $(TARGET) ; do gosh $$X >> test.log ; done

clean:
	rm -rf *~ test.log

M-x compile で試験できる。次の recur もサンプルで試験か。

;; test-recur.scm
(use gauche.test)

(add-load-path ".")
(load "heapbased")

(test-start "recur")

(test-section "3imp sample")
(test* "returns 5" 5 (recur count ([x '(a b c d e)])
			    (if (null? x)
				0
				(+ (count (cdr x)) 1))))

(test-end)

本当はもう少しきちんと試験せねば、なんでしょうがとりあえずこれでスルー。次は record を。これも簡単にこんなカンジ。本当はきちんと試験しないとダメなのでしょうね。

;; test-record.scm
(use gauche.test)

(add-load-path ".")
(load "heapbased")

(test-start "record")

(test-section "3imp sample")
(test* "returns (reverse '(1 2 3))" 
       '(3 2 1)
       (record (a b c) '(1 2 3) (list c b a)))

(test-end)

record-case のサンプルは面白そげ。不足気味ですが試験が以下

;; test-record-case.scm
(use gauche.test)

(add-load-path ".")
(load "heapbased")

(test-start "record-case")

(test-section "3imp sample")
(let ([p (rec calc
	      (lambda (x)
		(if (integer? x)
		    x
		    (record-case x
				 [+ (x y) (+ (calc x) (calc y))]
				 [* (x y) (* (calc x) (calc y))]
				 [- (x) (- 0 (calc x))]
				 [else (error "invalid exp")]))))])
  (test* "(p 1) is 1" 1 (p 1))
  (test* "(P (+ 2 3)) is 5" 5 (p (+ 2 3)))
  (test* "(p (* 2 3)) is 6" 6 (p (* 2 3)))
  (test* "(p (+ (* 2 3) 4) is 10" 10 (p (+ (* 2 3) 4)))
  (test* "(p (- 5)) is -5" -5 (p (- 5)))
  (test* "(p (+ (- 5) 5)) is 0" 0 (p (+ (- 5) 5)))
  (test* "other" *test-error* (p (/ 5 3)))
  )

(test-end)

一応上記の試験にはパス。かなりええ加減だなぁ。とりあえず tail? の試験を

;; test-compile.scm
(use gauche.test)

(add-load-path ".")
(load "heapbased")

(test-start "compile")

(test-section "tail?")
(test* "not tail" #f (tail? '(halt)))
(test* "tail" #t (tail? '(return)))

(test-end)

パスしたので本体の試験を検討ですが、とりあえず簡単なのから。

(test-section "constant")
(test* "(compile 1 '(halt)) returns (constant 1 (halt))"
       '(constant 1 (halt))
       (compile 1 '(halt)))

(test-section "symbol")
(test* "(compile 'x '(halt)) returns (refer x (halt))"
       '(refer x (halt))
       (compile 'x '(halt)))

残りは

  • quote
  • lambda
  • if
  • set!
  • call/cc
  • 手続き

なんですが、ここで再度エントリ投入。

続き

quote は簡単なはず、と以下の試験をでっち上げた。

(test-section "quote")
(test* "(compile '(quote x) '(halt)) returns (constant x (halt))"
       '(constant x (halt))
       (compile '(quote x) '(halt)))

試験失敗。(出力は整形してます

Testing compile ...                                              failed.
discrepancies found.  Errors are:
test (compile '(quote x) '(halt)) returns (constant x (halt)): 
  expects (constant x (halt)) => 
  got (frame (halt) (refer x (argument (refer quote (apply)))))

おかしいな。record-case が微妙? ええと record-case に入ってるとして call/cc なソレに入ってるように見えるぞ。やっぱ define-syntax が微妙なんだな。
ってか試験にパスしたんじゃねぇのかよ、と。よく試験と定義を見てみると key を quote しないと駄目なの? という事に気づく。これは試験が足りてません。とりあえず record-case の define-syntax を以下に修正したら試験パス。

(define-syntax record-case
  (syntax-rules (else)
    ((_ exp1 (else exp3 ...)) (begin exp3 ...))
    ((_ exp1 (key vars exp2 ...)) 
     (if (eq? (car exp1) 'key) (record vars (cdr exp1) exp2 ...)))
    ((_ exp1 (key vars exp2 ...) c ...)
     (if (eq? (car exp1) 'key) 
	 (record vars (cdr exp1) exp2 ...) 
	 (record-case exp1 c ...)))))

しかしどんな試験をしておくべきか。以下は微妙スギ??

(test-section "symbol")
(let ([p (lambda (x) (record-case x
				  [quote (obj)
					 (list 'constant obj)]
				  [lambda (vars body)
				    (list 'close vars body)]
				  [else
				   (list 'else)]))])
  (test* "(p '(quote x)) returns (constant x)"
	 '(constant x)
	 (p '(quote x)))
  (test* "(p '(lambda x y)) returns (close x y)"
	 '(close x y)
	 (p '(lambda x y)))
  (test* "(p '(xxx)) returns (else)"
	 '(else)
	 (p '(xxx)))
  )

いやはや。とりあえず買い物に行ってきます。