#lang scheme/base

(provide :
	 contract contract/arbitrary
	 define-contract
	 define/contract define-values/contract
	 -> mixed one-of predicate combined property)

(require deinprogramm/contract/contract
	 scheme/promise
	 (for-syntax scheme/base)
	 (for-syntax syntax/stx)
	 (for-syntax stepper/private/shared)
	 (only-in lang/private/teachprims beginner-equal?))

(define-for-syntax (phase-lift stx)
  (with-syntax ((?stx stx))
    (with-syntax ((?stx1 (syntax/loc stx #'?stx))) ; attach the occurrence position to the syntax object
      #'?stx1)))

(define-for-syntax (parse-contract name stx)
  (syntax-case* stx
		(mixed one-of predicate list -> combined property reference at contract)
		module-or-top-identifier=?
    ((mixed ?contract ...)
     (with-syntax ((?stx (phase-lift stx))
		   (?name name)
		   ((?contract-expr ...) (map (lambda (ctr)
						(parse-contract #f ctr))
					      (syntax->list #'(?contract ...)))))
       #'(make-mixed-contract '?name
			      (list ?contract-expr ...)
			      ?stx)))
    ((one-of ?exp ...)
     (with-syntax ((((?temp ?exp) ...) 
		    (map list
			 (generate-temporaries #'(?exp ...)) (syntax->list #'(?exp ...))))
		   (?stx (phase-lift stx))
		   (?name name))
       (with-syntax (((?check ...) 
		      (map (lambda (lis)
			     (with-syntax (((?temp ?exp) lis))
			       (with-syntax ((?raise
					      (syntax/loc 
					       #'?exp
					       (error 'contracts "hier kein Vertrag zulässig, nur normaler Wert"))))
				 #'(when (contract? ?temp)
				     ?raise))))
			   (syntax->list #'((?temp ?exp) ...)))))
       #'(let ((?temp ?exp) ...)
	   ?check ...
	   (make-case-contract '?name (list ?temp ...) beginner-equal? ?stx)))))
    ((predicate ?exp)
     (with-syntax ((?stx (phase-lift stx))
		   (?name name))
       #'(make-predicate-contract '?name (delay ?exp) ?stx)))
    ((list ?contract)
     (with-syntax ((?stx (phase-lift stx))
		   (?name name)
		   (?contract-expr (parse-contract #f #'?contract)))
       #'(make-list-contract '?name ?contract-expr ?stx)))
    ((list ?contract1 ?rest ...)
     (raise-syntax-error #f
			 "list-Vertrag darf nur einen Operanden haben."
			 (syntax ?contract1)))
    ((?arg-contract ... -> ?return-contract)
     (with-syntax ((?stx (phase-lift stx))
		   (?name name)
		   ((?arg-contract-exprs ...) (map (lambda (ctr)
						     (parse-contract #f ctr))
						   (syntax->list #'(?arg-contract ...))))
		   (?return-contract-expr (parse-contract #f #'?return-contract)))
       #'(make-procedure-contract '?name (list ?arg-contract-exprs ...) ?return-contract-expr ?stx)))
    ((?arg-contract ... -> ?return-contract1 ?return-contract2 . ?_)
     (raise-syntax-error #f
			 "Nach dem -> darf nur ein Vertrag stehen."
			 (syntax ?return-contract2)))
    ((at ?loc ?ctr)
     (with-syntax ((?ctr-expr (parse-contract #f #'?ctr)))
       #'(contract-update-syntax ?ctr-expr #'?loc)))
    (contract
     (with-syntax ((?stx (phase-lift stx)))
       #'(contract-update-syntax contract/contract #'?loc)))
    (?id
     (identifier? #'?id)
     (with-syntax ((?stx (phase-lift stx))
		   (?name name))
       (let ((name (symbol->string (syntax->datum #'?id))))
	 (if (char=? #\% (string-ref name 0))
	     #'(make-type-variable-contract '?id ?stx)
	     (with-syntax
		 ((?raise
		   (syntax/loc #'?stx
			       (error 'contracts "expected a contract, found ~e" ?id))))
	       (with-syntax
		   ((?ctr
		     #'(make-delayed-contract '?name
					      (delay
						(begin
						  (when (not (contract? ?id))
						    ?raise)
						  ?id)))))
		 ;; for local variables (parameters, most probably),
		 ;; we want the value to determine the blame location
		 (if (eq? (identifier-binding #'?id) 'lexical)
		     #'?ctr
		     #'(contract-update-syntax ?ctr #'?stx))))))))
    ((combined ?contract ...)
     (with-syntax ((?stx (phase-lift stx))
		   (?name name)
		   ((?contract-expr ...) (map (lambda (ctr)
						(parse-contract #f ctr))
					      (syntax->list #'(?contract ...)))))
       #'(make-combined-contract '?name
				 (list ?contract-expr ...)
				 ?stx)))
    ((property ?access ?contract)
     (with-syntax ((?stx (phase-lift stx))
		   (?name name)
		   (?contract-expr (parse-contract #f #'?contract)))
       #'(make-property-contract '?name
				 ?access
				 ?contract-expr
				 ?stx)))
    ((?contract-abstr ?contract ...)
     (identifier? #'?contract-abstr)
     (with-syntax ((?stx (phase-lift stx))
		   (?name name)
		   ((?contract-expr ...) (map (lambda (ctr)
						(parse-contract #f ctr))
					      (syntax->list #'(?contract ...)))))
       (with-syntax
	   ((?call (syntax/loc stx (?contract-abstr ?contract-expr ...))))
	 #'(make-call-contract '?name
			       (delay ?call)
			       (delay ?contract-abstr) (delay (list ?contract-expr ...))
			       ?stx))))
    (else
     (raise-syntax-error 'contract
			 "ungültiger Vertrag" stx))))

; regrettable
(define contract/contract
  (make-predicate-contract 'contract
			   (delay contract?)
			   #f))
			     

(define-syntax contract
  (lambda (stx)
    (syntax-case stx ()
      ((_ ?contr)
       #'(contract #f ?contr))
      ((_ ?name ?contr)
       (stepper-syntax-property
	(parse-contract (syntax->datum #'?name) #'?contr)
	'stepper-skip-completely #t)))))

(define-syntax contract/arbitrary
  (lambda (stx)
    (syntax-case stx ()
      ((_ ?arb ?contr . ?rest)
       #'(let ((contr (contract ?contr . ?rest)))
	   (set-contract-arbitrary! contr ?arb)
	   contr)))))

(define-syntax define-contract
  (lambda (stx)
    (syntax-case stx ()
      ((_ ?name ?ctr)
       (identifier? #'?name)
       (stepper-syntax-property #'(define ?name (contract ?name ?ctr))
				'stepper-skip-completely
				#t))
      ((_ (?name ?param ...) ?ctr)
       (and (identifier? #'?name)
	    (andmap identifier? (syntax->list #'(?param ...))))
       (stepper-syntax-property #'(define (?name ?param ...) (contract ?name ?ctr))
				'stepper-skip-completely
				#t)))))

(define-syntax define/contract
  (lambda (stx)
    (syntax-case stx ()
      ((_ ?name ?cnt ?expr)
       (with-syntax ((?enforced
		       (stepper-syntax-property #'(attach-name '?name (apply-contract/blame ?cnt ?expr))
						'stepper-skipto/discard
						;; apply-contract/blame takes care of itself
						;; remember there's an implicit #%app
						'(syntax-e cdr syntax-e cdr cdr car))))
							   
	 #'(define ?name ?enforced))))))

(define-syntax define-values/contract
  (lambda (stx)
    (syntax-case stx ()
      ((_ (?id ...) ?expr)
       (andmap identifier? (syntax->list #'(?id ...)))
       (syntax-track-origin
	#'(define-values (?id ...) ?expr)
	stx
	(car (syntax-e stx))))
      ((_ ((?id ?cnt)) ?expr)
       (identifier? #'?id)
       #'(define/contract ?id ?cnt ?expr)) ; works with stepper
      ((_ (?bind ...) ?expr)
       (let ((ids+enforced
	      (map (lambda (bind)
		     (syntax-case bind ()
		       (?id
			(identifier? #'?id)
			(cons #'?id #'?id))
		       ((?id ?cnt)
			(identifier? #'?id)
			(cons #'?id
			      #'(attach-name '?id (apply-contract/blame ?cnt ?id))))))
		   (syntax->list #'(?bind ...)))))
	 (with-syntax (((?id ...) (map car ids+enforced))
		       ((?enforced ...) (map cdr ids+enforced)))
	   (stepper-syntax-property
	    #'(define-values (?id ...)
		(call-with-values
		    (lambda () ?expr)
		  (lambda (?id ...)
		    (values ?enforced ...))))
	    'stepper-skip-completely #t)))))))

;; Matthew has promised a better way of doing this in the future.
(define (attach-name name thing)
  (if (procedure? thing)
      (procedure-rename thing name)
      thing))

(define-syntax :
  (syntax-rules ()
    ((: ?id ?ctr) (begin)))) ; probably never used, we're only interested in the binding for :

(define-for-syntax (within-contract-syntax-error stx name)
  (raise-syntax-error #f
		      "darf nur in Verträgen vorkommen"
		      name))

;; Expression -> Expression
;; Transforms unfinished code (... and the like) to code
;; raising an appropriate error.
(define-for-syntax within-contract-syntax-transformer
  (make-set!-transformer
   (lambda (stx)
     (syntax-case stx (set!)
       [(set! form expr) (within-contract-syntax-error stx (syntax form))]
       [(form . rest) (within-contract-syntax-error stx (syntax form))]
       [form (within-contract-syntax-error stx stx)]))))

(define-syntax -> within-contract-syntax-transformer)
(define-syntax mixed within-contract-syntax-transformer)
(define-syntax one-of within-contract-syntax-transformer)
(define-syntax predicate within-contract-syntax-transformer)
(define-syntax combined within-contract-syntax-transformer)
(define-syntax property within-contract-syntax-transformer)
; not a good idea:
; (define-syntax list within-contract-syntax-transformer)
