assert.ss
#lang scheme/base
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; BASE.plt
;;
;;
;; Bonzai Lab, LLC.  All rights reserved.
;;
;; Licensed under LGPL.
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; assert.ss - utility for verifying result of the values...
;; yc 1/9/2010 - fixed let/assert! and let*/assert to allow for optional test function
;; yc 2/10/2010 - move listof? to list.ss
(require (for-syntax scheme/base "args.ss")
         "base.ss"
         (only-in mzlib/etc identity)
         )

(define-struct (exn:assert! exn) (test? exp expected actual))

(define (error/assert! test? exp expected actual (name 'assert!))
  (raise (make-exn:assert! (if (not expected)
                               (format "~a assert! (~a ~a); actual ~a" name test? exp actual)
                               (format "~a assert! (~a ~a ~a); actual ~a" name test? exp expected actual))
                           (current-continuation-marks) test? exp expected actual)))

;; assert! v test? v2
;; assert! v true?
;; assert! v v2 (use equal for comparison) => we can get rid of this form...
(define-syntax named-assert!
  (syntax-rules () 
    ((~ name exp test? expected)
     (let ((actual exp))
       (if (test? actual expected)
           actual
           (error/assert! 'test? 'exp 'expected actual 'name))))
    ((~ name exp test?)
     (let ((actual exp))
       (if (test? actual)
           actual
           (error/assert! 'test? 'exp #f actual 'name))))
    ((~ name exp)
     (named-assert! name exp identity))
    ))

(define-syntax assert!
  (syntax-rules () 
    ((~ args ...)
     (named-assert! assert! args ...))))


(define-syntax let/assert!
  (syntax-rules () 
    ((~ ((id test? arg) ...) exp exp2 ...)
     (let/assert! let/assert! ((id test? arg) ...) exp exp2 ...))
    ((~ name ((id test? arg) ...) exp exp2 ...)
     (let ((id arg) ...)
       (let ((id (named-assert! name id test?)) ...) exp exp2 ...)))
    ((~ ((id arg) ...) exp exp2 ...)
     (let/assert! let/assert! ((id identity arg) ...) exp exp2 ...))
    ((~ name ((id arg) ...) exp exp2 ...) 
     (let/assert! name ((id identity arg) ...) exp exp2 ...)) 
    ))

(define-syntax let*/assert! 
  (syntax-rules ()
    ((~ name () exp exp2 ...)
     (begin exp exp2 ...))
    ((~ ((id test? arg) ...) exp exp2 ...)
     (let*/assert! let*/assert! ((id test? arg) ...) exp exp2 ...))
    ((~ name ((id test? arg) rest ...) exp exp2 ...)
     (let/assert! name ((id test? arg))
                  (let*/assert! name (rest ...) exp exp2 ...)))
    ((~ ((id arg) ...) exp exp2 ...)
     (let*/assert! ((id identity arg) ...) exp exp2 ...))
    ((~ name ((id arg) ...) exp exp2 ...) 
     (let*/assert! name ((id identity arg) ...) exp exp2 ...))
    ))

(define-syntax (lambda/assert! stx)
  (syntax-case stx () 
    ((~ name (a1 ... rest-id rest-type) exp exp2 ...) 
     (and (symbol? (syntax->datum #'name)) 
          (symbol? (syntax->datum #'rest-id)))
     (with-syntax (((arg ...)
                    (typed-args->args #'(a1 ...)))
                   ((id ...)
                    (args->identifiers #'(a1 ...)))
                   ((type ...)
                    (typed-args->types #'(a1 ...)))
                   )
       #'(lambda (arg ... . rest-id) 
           (let/assert! name ((id type id) ...
                              (rest-id rest-type rest-id))
                        exp exp2 ...))))
    ((~ name (a1 ...) exp exp2 ...) 
     (symbol? (syntax->datum #'name)) 
     (with-syntax (((arg ...)
                    (typed-args->args #'(a1 ...)))
                   ((id ...)
                    (args->identifiers #'(a1 ...)))
                   ((type ...)
                    (typed-args->types #'(a1 ...)))
                   )
       #'(lambda (arg ...) ;; this is the general idea.. but this general idea doesn't fully work...
           (let/assert! name ((id type id) ...)
                        exp exp2 ...))))
    ((~ (a1 ...) exp exp2 ...)
     #'(~ lambda/assert! (a1 ...) exp exp2 ...))
    ))
  
(define-syntax define/assert!
  (syntax-rules () 
    ((~ (name . args) exp exp2 ...)
     (define name 
       (lambda/assert! name args exp exp2 ...)))))

(provide define/assert!
         lambda/assert!
         let*/assert!
         let/assert!
         assert!
         named-assert!
         )

(provide/contract 
 (struct exn:assert! ((message string?)
                      (continuation-marks continuation-mark-set?)
                      (test? any/c)
                      (exp any/c)
                      (expected any/c)
                      (actual any/c)))
 (error/assert! (->* (any/c any/c any/c any/c)
                     (symbol?)
                     any))
 )

#|
;; if I want to define a contract... with the following form it can become quite complicated!!! 

;; we can also guard the arguments @ regular lamda and also let statement... 
;; guarding the arguments...
(define/assert! (foo (a number?) (b number? 5) #:c (c number? 5))
    (+ a b c))

(define/assert! (foo2 (a number?) (b number? 10) . (rest (listof? number?)))
  (apply + a b rest))
(let/assert! ((a number? 3) (b number? 'abc)) 
             (+ a b))
;;|#