racket/collects/deinprogramm/contract/contract.ss
Mike Sperber 018521cbc3 Merge from mike/dmda branch.
This adds the language levels, teachpacks, and documentation for the
textbook "Die Macht der Abstraktion".

svn: r14019
2009-03-09 07:51:09 +00:00

253 lines
7.3 KiB
Scheme

#lang scheme/base
(provide contract?
contract-name contract-syntax
contract-violation-proc
call-with-contract-violation-proc
make-delayed-contract
make-property-contract
make-predicate-contract
make-type-variable-contract
make-list-contract
make-mixed-contract
make-combined-contract
make-case-contract
make-procedure-contract
contract-update-syntax
apply-contract apply-contract/blame)
(require scheme/promise
(for-syntax scheme/base)
(for-syntax stepper/private/shared))
; name may be #f
; enforcer: contract val -> val
;
; syntax: syntax data from where the contract was defined
(define-struct contract (name enforcer syntax))
(define (contract-update-syntax ctr stx)
(struct-copy contract ctr (syntax stx)))
; message may be #f
(define contract-violation-proc (make-parameter (lambda (obj contract message blame)
(raise (make-exn:fail:contract (or message
(format "got ~e" obj))
(current-continuation-marks))))))
(define (contract-violation obj contract msg blame)
((contract-violation-proc) obj contract msg blame))
(define (call-with-contract-violation-proc proc thunk)
(parameterize ((contract-violation-proc proc))
(thunk)))
(define (make-delayed-contract name promise syntax)
(make-contract name
(lambda (self obj)
((contract-enforcer (force promise)) self obj))
syntax))
(define (make-property-contract name access contract syntax)
(let ((enforce (contract-enforcer contract)))
(make-contract name
(lambda (self obj)
(enforce self (access obj)) ; #### problematic: enforcement doesn't stick
obj)
syntax)))
(define (make-predicate-contract name predicate-promise syntax)
(make-contract
name
(lambda (self obj) ; dynamic binding because of syntax remapping via `contract-update-syntax'
(if ((force predicate-promise) obj)
obj
(begin
(contract-violation obj self #f #f)
obj)))
syntax))
(define (make-type-variable-contract name syntax)
(make-predicate-contract name (lambda (obj) #t) syntax))
; maps lists to pairs of contract, enforced value
(define lists-table (make-weak-hasheq))
(define (make-list-contract name arg-contract syntax)
(make-contract
name
(lambda (self obj)
;;(write (list 'list obj) (current-error-port)) (newline (current-error-port))
(let recur ((l obj))
(define (go-on)
(let ((enforced (cons (apply-contract arg-contract (car l))
(recur (cdr l)))))
(hash-set! lists-table l (cons self enforced))
(hash-set! lists-table enforced (cons self enforced))
enforced))
(cond
((null? l)
l)
((not (pair? l))
(contract-violation obj self #f #f)
obj)
((hash-ref lists-table l #f)
=> (lambda (seen)
;;(write (list 'seen seen (eq? self (car seen))) (current-error-port)) (newline (current-error-port))
(if (eq? self (car seen))
(cdr seen)
(go-on))))
(else
(go-on)))))
syntax))
(define (make-mixed-contract name alternative-contracts syntax)
(make-contract
name
(lambda (self obj)
(let loop ((alternative-contracts alternative-contracts))
(if (null? alternative-contracts)
(begin
(contract-violation obj self #f #f)
obj)
((let/ec exit
(let ((enforced
(call-with-contract-violation-proc
(lambda (contract syntax msg blame)
(exit (lambda () (loop (cdr alternative-contracts)))))
(lambda ()
(let ((ctr (car alternative-contracts)))
(if (eq? ctr self)
(raise
(make-exn:fail:contract
(string->immutable-string
(if name
(format "rekursiver Vertrag: ~a" name)
"rekursiver Vertrag"))
(current-continuation-marks)))
(apply-contract ctr obj)))))))
(lambda () enforced)))))))
syntax))
(define (make-combined-contract name contracts syntax)
(make-contract
name
(lambda (self obj)
(let ((old-violation-proc (contract-violation-proc)))
((let/ec exit
(call-with-contract-violation-proc
(lambda (contract syntax msg blame)
(exit
(lambda ()
(old-violation-proc contract syntax msg blame)
obj)))
(lambda ()
(let loop ((contracts contracts)
(obj obj))
(if (null? contracts)
(lambda () obj)
(loop (cdr contracts)
(apply-contract (car contracts) obj))))))))))
syntax))
(define (make-case-contract name cases syntax)
(make-contract
name
(lambda (self obj)
(let loop ((cases cases))
(cond
((null? cases)
(contract-violation obj self #f #f)
obj)
((equal? (car cases) obj)
obj)
(else
(loop (cdr cases))))))
syntax))
(define-struct procedure-to-blame (proc syntax))
(define (make-procedure-contract name arg-contracts return-contract syntax)
(let ((arg-count (length arg-contracts)))
(make-contract
name
(lambda (self thing)
(let-values (((proc blame-syntax)
(if (procedure-to-blame? thing)
(values (procedure-to-blame-proc thing)
(procedure-to-blame-syntax thing))
(values thing #f))))
(cond
((not (procedure? proc))
(contract-violation proc self #f #f))
((not (procedure-arity-includes? proc arg-count)) ; #### variable arity
(contract-violation proc self "wrong number of parameters" #f)))
(attach-name
(object-name proc)
(lambda args
(if (not (= (length args) arg-count))
(begin
(contract-violation proc self "wrong number of arguments" #f)
(apply-contract return-contract (apply proc args)))
(let* ((old-violation-proc (contract-violation-proc))
(arg-violation? #f)
(args
(call-with-contract-violation-proc
(lambda (obj contract message blame)
(set! arg-violation? #t)
(old-violation-proc obj contract message blame))
(lambda ()
(map apply-contract arg-contracts args))))
(retval (apply proc args)))
(if arg-violation?
retval
(call-with-contract-violation-proc
(lambda (obj contract message _)
;; blame the procedure
(old-violation-proc obj contract message blame-syntax))
(lambda ()
(apply-contract return-contract retval))))))))))
syntax)))
;; Matthew has promised a better way of doing this in the future.
(define (attach-name name thing)
(if (and (procedure? thing)
(symbol? name))
(procedure-rename thing name)
thing))
; like apply-contract, but can track more precise blame into the contract itself
(define-syntax apply-contract/blame
(lambda (stx)
(syntax-case stx ()
((_ ?cnt-exp ?val-exp)
(syntax-case (local-expand #'?val-exp 'expression #f) (lambda #%plain-lambda)
((lambda ?params ?body0 ?body1 ...)
(stepper-syntax-property
;; remember there's an implicit #%app
#'(apply-contract ?cnt-exp
(make-procedure-to-blame ?val-exp
#'?val-exp))
'stepper-skipto/discard
'(syntax-e cdr syntax-e cdr cdr car
syntax-e cdr syntax-e cdr car)))
((#%plain-lambda ?params ?body0 ?body1 ...)
(stepper-syntax-property
#'(apply-contract ?cnt-exp
(make-procedure-to-blame ?val-exp
#'?val-exp))
'stepper-skipto/discard
'(syntax-e cdr syntax-e cdr cdr car
syntax-e cdr syntax-e cdr car)))
(_
(stepper-syntax-property
#'(apply-contract ?cnt-exp ?val-exp)
'stepper-skipto/discard
'(syntax-e cdr syntax-e cdr cdr car))))))))
(define (apply-contract contract val)
((contract-enforcer contract) contract val))