#lang scheme/base (provide module-begin) (require deinprogramm/define-record-procedures deinprogramm/contract/contract-syntax) (require (for-syntax scheme/base) (for-syntax mzlib/list) (for-syntax syntax/boundmap) (for-syntax syntax/kerncase)) (define-syntax (print-results stx) (syntax-case stx () ((_ expr) (not (or (syntax-property #'expr 'stepper-hide-completed) (syntax-property #'expr 'stepper-skip-completely) (syntax-property #'expr 'test-call))) (syntax-property (syntax-property #'(#%app call-with-values (lambda () expr) do-print-results) 'stepper-skipto '(syntax-e cdr cdr car syntax-e cdr cdr car)) 'certify-mode 'transparent)) ((_ expr) #'expr))) (define (do-print-results . vs) (for-each (current-print) vs) ;; Returning 0 values avoids any further result printing ;; (even if void values are printed) (values)) (define-syntaxes (module-begin module-continue) (let () ;; takes a list of syntax objects (the result of syntax-e) and returns all the syntax objects that correspond to ;; a contract declaration. Syntax: (: id contract) (define extract-contracts (lambda (lostx) (let* ((table (make-bound-identifier-mapping)) (non-contracts (filter (lambda (maybe) (syntax-case maybe (:) ((: ?id ?cnt) (identifier? #'id) (begin (when (bound-identifier-mapping-get table #'?id (lambda () #f)) (raise-syntax-error #f "Zweite Vertragsdefinition für denselben Namen." maybe)) (bound-identifier-mapping-put! table #'?id #'?cnt) #f)) ((: ?id) (raise-syntax-error 'contracts "Bei dieser Vertragsdefinition fehlt der Vertrag" maybe)) ((: ?id ?cnt ?stuff0 ?stuff1 ...) (raise-syntax-error 'contracts "In der :-Form werden ein Name und ein Vertrag erwartet; da steht noch mehr" (syntax/loc #'?stuff0 (?stuff0 ?stuff1 ...)))) (_ #t))) lostx))) (values table non-contracts)))) (define local-expand-stop-list (append (list #': #'define-contract #'#%require #'#%provide) (kernel-form-identifier-list))) (define (expand-contract-expressions contract-table expressions) (let loop ((exprs expressions)) (cond ((null? exprs) (bound-identifier-mapping-for-each contract-table (lambda (id thing) (when thing (if (identifier-binding id) (raise-syntax-error #f "Zu einer eingebauten Form kann kein Vertrag erklärt werden" id) (raise-syntax-error #f "Zu diesem Vertrag gibt es keine Definition" id))))) #'(begin)) (else (let ((expanded (car exprs))) (syntax-case expanded (begin define-values) ((define-values (?id ...) ?e1) (with-syntax (((?enforced ...) (map (lambda (id) (with-syntax ((?id id)) (cond ((bound-identifier-mapping-get contract-table #'?id (lambda () #f)) => (lambda (cnt) (bound-identifier-mapping-put! contract-table #'?id #f) ; check for orphaned contracts (with-syntax ((?cnt cnt)) #'(?id ?cnt)))) (else #'?id)))) (syntax->list #'(?id ...)))) (?rest (loop (cdr exprs)))) (with-syntax ((?defn (syntax-track-origin #'(define-values/contract (?enforced ...) ?e1) (car exprs) (car (syntax-e expanded))))) (syntax/loc (car exprs) (begin ?defn ?rest))))) ((begin e1 ...) (loop (append (syntax-e (syntax (e1 ...))) (cdr exprs)))) (else (with-syntax ((?first expanded) (?rest (loop (cdr exprs)))) (syntax/loc (car exprs) (begin ?first ?rest)))))))))) (values ;; module-begin (lambda (stx) (syntax-case stx () ((_ e1 ...) ;; module-begin-continue takes a sequence of expanded ;; exprs and a sequence of to-expand exprs; that way, ;; the module-expansion machinery can be used to handle ;; requires, etc.: #`(#%plain-module-begin (module-continue (e1 ...) () ()))))) ;; module-continue (lambda (stx) (syntax-case stx () ((_ () (e1 ...) (defined-id ...)) ;; Local-expanded all body elements, lifted out requires, etc. ;; Now process the result. (begin ;; The expansion for contracts breaks the way that beginner-define, etc., ;; check for duplicate definitions, so we have to re-check here. ;; A better strategy might be to turn every define into a define-syntax ;; to redirect the binding, and then the identifier-binding check in ;; beginner-define, etc. will work. (let ((defined-ids (make-bound-identifier-mapping))) (for-each (lambda (id) (when (bound-identifier-mapping-get defined-ids id (lambda () #f)) (raise-syntax-error #f "Für diesen Namen gibt es schon eine Definition." id)) (bound-identifier-mapping-put! defined-ids id #t)) (reverse (syntax->list #'(defined-id ...))))) ;; Now handle contracts: (let ((top-level (reverse (syntax->list (syntax (e1 ...)))))) (let-values (((cnt-table expr-list) (extract-contracts top-level))) (expand-contract-expressions cnt-table expr-list))))) ((frm e3s e1s def-ids) (let loop ((e3s #'e3s) (e1s #'e1s) (def-ids #'def-ids)) (syntax-case e3s () (() #`(frm () #,e1s #,def-ids)) ((e2 . e3s) (let ((e2 (local-expand #'e2 'module local-expand-stop-list))) ;; Lift out certain forms to make them visible to the module ;; expander: (syntax-case e2 (#%require #%provide define-syntaxes define-values-for-syntax define-values begin define-record-procedures define-record-procedures-2 define-record-procedures-parametric define-record-procedures-parametric-2 define-contract :) ((#%require . __) #`(begin #,e2 (frm e3s #,e1s #,def-ids))) ((#%provide . __) #`(begin #,e2 (frm e3s #,e1s #,def-ids))) ((define-syntaxes (id ...) . _) #`(begin #,e2 (frm e3s #,e1s (id ... . #,def-ids)))) ((define-values-for-syntax . _) #`(begin #,e2 (frm e3s #,e1s #,def-ids))) ((begin b1 ...) (syntax-track-origin (loop (append (syntax->list #'(b1 ...)) #'e3s) e1s def-ids) e2 (car (syntax-e e2)))) ((define-values (id ...) . _) (loop #'e3s (cons e2 e1s) (append (syntax->list #'(id ...)) def-ids))) ((define-contract id ctr) (loop #'e3s (cons e2 e1s) def-ids)) ((define-record-procedures id cns prd (spec ...)) (loop #'e3s (cons e2 e1s) def-ids)) ((define-record-procedures-2 id cns prd (spec ...)) (loop #'e3s (cons e2 e1s) def-ids)) ((define-record-procedures-parametric id cns prd (spec ...)) (loop #'e3s (cons e2 e1s) def-ids)) ((define-record-procedures-parametric-2 id cns prd (spec ...)) (loop #'e3s (cons e2 e1s) def-ids)) ((: stuff ...) (loop #'e3s (cons e2 e1s) def-ids)) (_ (loop #'e3s (cons #`(print-results #,e2) e1s) def-ids)))))))))))))