;;; r6rs-records-procedural.test --- Test suite for R6RS 
;;; (rnrs records procedural)

;;      Copyright (C) 2010 Free Software Foundation, Inc.
;;
;; This library is free software; you can redistribute it and/or
;; modify it under the terms of the GNU Lesser General Public
;; License as published by the Free Software Foundation; either
;; version 3 of the License, or (at your option) any later version.
;; 
;; This library is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; Lesser General Public License for more details.
;; 
;; You should have received a copy of the GNU Lesser General Public
;; License along with this library; if not, write to the Free Software
;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA


(define-module (test-suite test-rnrs-records-procedural)
  :use-module ((rnrs conditions) :version (6))
  :use-module ((rnrs exceptions) :version (6))
  :use-module ((rnrs records procedural) :version (6))
  :use-module (test-suite lib))

(define :point (make-record-type-descriptor 
		'point #f #f #f #f '#((mutable x) (mutable y))))
(define :point-cd (make-record-constructor-descriptor :point #f #f))

(define :voxel (make-record-type-descriptor 
		'voxel :point #f #f #f '#((mutable z))))
(define :voxel-cd (make-record-constructor-descriptor :voxel :point-cd #f))

(with-test-prefix "make-record-type-descriptor"
  (pass-if "simple"
    (let* ((:point-cd (make-record-constructor-descriptor :point #f #f))
	   (make-point (record-constructor :point-cd))
	   (point? (record-predicate :point))
	   (point-x (record-accessor :point 0))
	   (point-y (record-accessor :point 1))
	   (point-x-set! (record-mutator :point 0))
	   (point-y-set! (record-mutator :point 1))
	   (p1 (make-point 1 2)))
      (point? p1)
      (eqv? (point-x p1) 1)
      (eqv? (point-y p1) 2)
      (unspecified? (point-x-set! p1 5))
      (eqv? (point-x p1) 5)))

  (pass-if "sealed records cannot be subtyped"
    (let* ((:sealed-point (make-record-type-descriptor 
			   'sealed-point #f #f #t #f '#((mutable x) 
							(mutable y))))
	   (success #f))
      (call/cc 
       (lambda (continuation)
	 (with-exception-handler
	  (lambda (condition) 
	    (set! success (assertion-violation? condition))
	    (continuation))
	  (lambda () (make-record-type-descriptor
		      'sealed-point-subtype :sealed-point #f #f #f
		      '#((mutable z)))))))
      success))

  (pass-if "non-generative records with same uid are eq"
    (let* ((:rtd-1 (make-record-type-descriptor 
		    'rtd1 #f 'my-uid #f #f '#((mutable foo) (immutable bar))))
	   (:rtd-2 (make-record-type-descriptor
		    'rtd1 #f 'my-uid #f #f '#((mutable foo) (immutable bar)))))
       (eq? :rtd-1 :rtd-2)))

  (pass-if "&assertion raised on conflicting non-generative types"
    (let* ((:rtd-1 (make-record-type-descriptor
		    'rtd1 #f 'my-uid-2 #f #f '#((mutable foo) (immutable bar))))
	   (success 0)
	   (check-definition
	    (lambda (thunk)
	      (call/cc 
	       (lambda (continuation)
		 (with-exception-handler
		  (lambda (condition)
		    (if (assertion-violation? condition)
			(set! success (+ success 1)))
		    (continuation))
		  thunk))))))
      (check-definition
       (lambda () 
	 (make-record-type-descriptor
	  'rtd1a #f 'my-uid-2 #f #f '#((mutable foo) (immutable bar)))))
      (check-definition
       (lambda ()
	 (make-record-type-descriptor
	  'rtd1 :point 'my-uid-2 #f #f '#((mutable foo) (immutable bar)))))
      (check-definition
       (lambda ()
	 (make-record-type-descriptor
	  'rtd1 #f 'my-uid-2 #t #f '#((mutable foo) (immutable bar)))))
      (check-definition
       (lambda ()
	 (make-record-type-descriptor
	  'rtd1 #f 'my-uid-2 #f #t '#((mutable foo) (immutable bar)))))
      (check-definition
       (lambda () (make-record-type-descriptor 'rtd1 #f 'my-uid-2 #f #f '#())))
      (check-definition
       (lambda () 
	 (make-record-type-descriptor 
	  'rtd1 #f 'my-uid-2 #f #f '#((mutable foo) (immutable baz)))))
      (check-definition
       (lambda () 
	 (make-record-type-descriptor 
	  'rtd1 #f 'my-uid-2 #f #f '#((immutable foo) (immutable bar)))))
      (eqv? success 7))))

(with-test-prefix "make-record-constructor-descriptor"
  (pass-if "simple protocol"
    (let* ((:point-protocol (lambda (p) (lambda (x y) (p (+ x 1) (+ y 1)))))
	   (:point-protocol-cd (make-record-constructor-descriptor 
				:point #f :point-protocol))
	   (make-point (record-constructor :point-protocol-cd))
	   (point-x (record-accessor :point 0))
	   (point-y (record-accessor :point 1))
	   (point (make-point 1 2)))
      (and (eqv? (point-x point) 2)
	   (eqv? (point-y point) 3))))

  (pass-if "protocol delegates to parent with protocol"
    (let* ((:point-protocol (lambda (p) (lambda (x y) (p (+ x 1) (+ y 1)))))
	   (:point-protocol-cd (make-record-constructor-descriptor
				:point #f :point-protocol))
	   (:voxel-protocol (lambda (n) 
			      (lambda (x y z)
				(let ((p (n x y))) (p (+ z 100))))))
	   (:voxel-protocol-cd (make-record-constructor-descriptor
				:voxel :point-protocol-cd :voxel-protocol))
	   (make-voxel (record-constructor :voxel-protocol-cd))
	   (point-x (record-accessor :point 0))
	   (point-y (record-accessor :point 1))
	   (voxel-z (record-accessor :voxel 0))
	   (voxel (make-voxel 1 2 3)))
      (and (eqv? (point-x voxel) 2)
	   (eqv? (point-y voxel) 3)
	   (eqv? (voxel-z voxel) 103)))))      

(with-test-prefix "record-type-descriptor?"
  (pass-if "simple"
    (record-type-descriptor? 
     (make-record-type-descriptor 'test #f #f #f #f '#()))))

(with-test-prefix "record-constructor"
  (pass-if "simple"
    (let* ((make-point (record-constructor :point-cd))
	   (point? (record-predicate :point))
	   (point-x (record-accessor :point 0))
	   (point-y (record-accessor :point 1))
	   (point (make-point 1 2)))
      (and (point? point)
	   (eqv? (point-x point) 1)
	   (eqv? (point-y point) 2))))

  (pass-if "construct record subtype"
    (let* ((make-voxel (record-constructor :voxel-cd))
	   (voxel? (record-predicate :voxel))
	   (voxel-z (record-accessor :voxel 0))
	   (voxel (make-voxel 1 2 3)))
      (and (voxel? voxel)
	   (eqv? (voxel-z voxel) 3)))))

(with-test-prefix "record-predicate"
  (pass-if "simple"
    (let* ((make-point (record-constructor :point-cd))
	   (point (make-point 1 2))
	   (point? (record-predicate :point)))
      (point? point)))

  (pass-if "predicate returns true on subtype"
    (let* ((make-voxel (record-constructor :voxel-cd))
	   (voxel (make-voxel 1 2 3))
	   (point? (record-predicate :point)))
      (point? voxel)))

  (pass-if "predicate returns false on supertype"
    (let* ((make-point (record-constructor :point-cd))
	   (point (make-point 1 2))
	   (voxel? (record-predicate :voxel)))
      (not (voxel? point)))))

(with-test-prefix "record-accessor"
  (pass-if "simple"
    (let* ((make-point (record-constructor :point-cd))
	   (point (make-point 1 2))
	   (point-x (record-accessor :point 0))
	   (point-y (record-accessor :point 1)))
      (and (eqv? (point-x point) 1)
	   (eqv? (point-y point) 2))))

  (pass-if "accessor for supertype applied to subtype"
    (let* ((make-voxel (record-constructor :voxel-cd))
	   (voxel (make-voxel 1 2 3))
	   (point-x (record-accessor :point 0))
	   (point-y (record-accessor :point 1)))
      (and (eqv? (point-x voxel) 1)
	   (eqv? (point-y voxel) 2)))))

(with-test-prefix "record-mutator"
  (pass-if "simple"
    (let* ((make-point (record-constructor :point-cd))
	   (point (make-point 1 2))
	   (point-set-x! (record-mutator :point 0))
	   (point-set-y! (record-mutator :point 1))
	   (point-x (record-accessor :point 0))
	   (point-y (record-accessor :point 1)))
      (point-set-x! point 3)
      (point-set-y! point 4)
      (and (eqv? (point-x point) 3)
	   (eqv? (point-y point) 4))))

  (pass-if "&assertion raised on request for immutable field"
    (let* ((:immutable-point (make-record-type-descriptor 
			      'point #f #f #f #f '#((immutable x) 
						    (immutable y))))
	   (success #f))
      (call/cc 
       (lambda (continuation)
	 (with-exception-handler
	  (lambda (condition) 
	    (set! success (assertion-violation? condition))
	    (continuation))
	  (lambda () (record-mutator :immutable-point 0)))))
      success))
	 
  (pass-if "mutator for supertype applied to subtype"
    (let* ((make-voxel (record-constructor :voxel-cd))
	   (voxel (make-voxel 1 2 3))
	   (point-set-x! (record-mutator :point 0))
	   (point-set-y! (record-mutator :point 1))
	   (point-x (record-accessor :point 0))
	   (point-y (record-accessor :point 1)))
      (point-set-x! voxel 3)
      (point-set-y! voxel 4)
      (and (eqv? (point-x voxel) 3)
	   (eqv? (point-y voxel) 4)))))

