(module kw '#%kernel (#%require "define.ss" "small-scheme.ss" "more-scheme.ss" (for-syntax '#%kernel "stx.ss" "small-scheme.ss" "stxcase-scheme.ss" "name.ss" "norm-define.ss" "qqstx.ss" "sort.ss")) (#%provide new-lambda new-λ new-define new-app (rename *make-keyword-procedure make-keyword-procedure) keyword-apply procedure-keywords procedure-reduce-keyword-arity new-prop:procedure) ;; ---------------------------------------- (-define-struct keyword-procedure (proc required allowed)) (define-values (struct:keyword-method make-km keyword-method? km-ref km-set!) (make-struct-type 'procedure struct:keyword-procedure 0 0 #f)) (define (generate-arity-string proc) (let-values ([(req allowed) (procedure-keywords proc)] [(a) (procedure-arity proc)] [(keywords-desc) (lambda (opt req) (format "~a with keyword~a~a" (if (null? (cdr req)) (format "an ~aargument" opt) (format "~aarguments" opt)) (if (null? (cdr req)) "" "s") (case (length req) [(1) (format " ~a" (car req))] [(2) (format " ~a and ~a" (car req) (cadr req))] [else (let loop ([req req]) (if (null? (cdr req)) (format " and ~a" (car req)) (format " ~a,~a" (car req) (loop (cdr req)))))])))] [(method-adjust) (lambda (a) (if (or (okm? proc) (keyword-method? proc)) (if (zero? a) 0 (sub1 a)) a))]) (string-append (cond [(number? a) (let ([a (method-adjust a)]) (format "~a argument~a" a (if (= a 1) "" "s")))] [(arity-at-least? a) (let ([a (method-adjust (arity-at-least-value a))]) (format "at least ~a argument~a" a (if (= a 1) "" "s")))] [else "a different number of arguments"]) (if (null? req) "" (format " plus ~a" (keywords-desc "" req))) (if allowed (let ([others (let loop ([req req][allowed allowed]) (cond [(null? req) allowed] [(eq? (car req) (car allowed)) (loop (cdr req) (cdr allowed))] [else (cons (car allowed) (loop req (cdr allowed)))]))]) (if (null? others) "" (format " plus ~a" (keywords-desc "optional " others)))) " plus arbitrary keyword arguments")))) ;; Constructor for a procedure with only optional keywords. ;; The `procedure' property dispatches to a procedure in the ;; struct (which has exactly the right arity). (define-values (struct:okp make-optional-keyword-procedure okp? okp-ref okp-set!) (make-struct-type 'procedure struct:keyword-procedure 1 0 #f (list (cons prop:arity-string generate-arity-string)) (current-inspector) 0)) ;; A ``method'' (for arity reporting) (define-values (struct:okm make-optional-keyword-method okm? okm-ref okm-set!) (make-struct-type 'procedure struct:okp 0 0 #f)) ;; Constructor generator for a procedure with a required keyword. ;; (This is used with lift-expression, so that the same constructor ;; is used for each evaluation of a keyword lambda.) ;; The `procedure' property is a per-type method that has exactly ;; the right arity, and that sends all arguments to `missing-kw'. (define (make-required name fail-proc method?) (let-values ([(s: mk ? -ref -set!) (make-struct-type (string->symbol (format "procedure:~a" name)) (if method? struct:keyword-method struct:keyword-procedure) 0 0 #f (list (cons prop:arity-string generate-arity-string)) (current-inspector) fail-proc)]) mk)) ;; Allows keyword application to see into a "method"-style procedure attribute: (define-values (new-prop:procedure new-procedure? new-procedure-ref) (make-struct-type-property 'procedure #f (list (cons prop:procedure values)))) ;; ---------------------------------------- (define *make-keyword-procedure (letrec ([make-keyword-procedure (case-lambda [(proc) (make-keyword-procedure proc (lambda args (apply proc null null args)))] [(proc plain-proc) (make-optional-keyword-procedure proc null #f plain-proc)])]) make-keyword-procedure)) (define (keyword-apply proc kws kw-vals . normal-argss) (let ([type-error (lambda (what which) (apply raise-type-error 'keyword-apply what which proc kws kw-vals normal-argss))]) (unless (procedure? proc) (type-error "procedure" 0)) (let loop ([ks kws]) (cond [(null? ks) (void)] [(or (not (pair? ks)) (not (keyword? (car ks)))) (type-error "list of keywords" 1)] [(null? (cdr ks)) (void)] [(or (not (pair? (cdr ks))) (not (keyword? (cadr ks)))) (loop (cdr ks))] [(keywordlist #'(id ... . rest)))]) (when dup-id (raise-syntax-error #f "duplicate argument identifier" stx dup-id))) (let* ([kws (syntax->list #'(kw ...))] [opts (syntax->list #'(opt-id ...))] [ids (syntax->list #'(id ...))] [plain-ids (syntax->list #'(plain-id ...))] [kw-reqs (syntax->list #'(kw-req ...))] [kw-args (generate-temporaries kws)] ; to hold supplied value [kw-arg?s (generate-temporaries kws)] ; to indicated whether it was supplied [opt-args (generate-temporaries opts)] ; supplied value [opt-arg?s (generate-temporaries opts)] ; whether supplied [needed-kws (sort (syntax->list #'need-kw) (lambda (a b) (keywordlist stx)]) (if (not (and l (pair? (cdr l)) (not (keyword? (syntax-e (cadr l)))) (ormap (lambda (x) (keyword? (syntax-e x))) l))) ;; simple or erroneous app: (if (identifier? stx) (raise-syntax-error #f "illegal use" stx) (if (and (pair? l) (null? (cdr l))) (raise-syntax-error #f "missing procedure expression; probably originally (), which is an illegal empty application" stx) (quasisyntax/loc stx (#%app . #,(cdr (syntax-e stx)))))) ;; keyword app (maybe) (let ([exprs (let ([kw-ht (make-hasheq)]) (let loop ([l (cddr l)]) (cond [(null? l) null] [(keyword? (syntax-e (car l))) (when (hash-ref kw-ht (syntax-e (car l)) #f) (raise-syntax-error 'application "duplicate keyword in application" stx (car l))) (hash-set! kw-ht (syntax-e (car l)) #t) (cond [(null? (cdr l)) (raise-syntax-error 'application "missing argument expression after keyword" stx (car l))] [(keyword? (cadr l)) (raise-syntax-error 'application "keyword in expression possition (immediately after another keyword)" stx (cadr l))] [else (cons (cadr l) (loop (cddr l)))])] [else (cons (car l) (loop (cdr l)))])))]) (let ([ids (cons (or (syntax-local-infer-name stx) 'procedure) (generate-temporaries exprs))]) (let loop ([l (cdr l)] [ids ids] [bind-accum null] [arg-accum null] [kw-pairs null]) (cond [(null? l) (let* ([args (reverse arg-accum)] [sorted-kws (sort kw-pairs (lambda (a b) (keyword all keywords are allowed (loop (cdr kws) required #f)] [(pair? allowed) (if (eq? (car allowed) (car kws)) (loop (cdr kws) required (cdr allowed)) (loop kws required (cdr allowed)))] [else (values #f (car kws))]))) ;; Extracts the procedure using the keyword-argument protocol. ;; If `p' doesn't accept keywords, make up a procedure that ;; reports an error. (define (keyword-procedure-extract/method kws n p method-n) (if (and (keyword-procedure? p) (procedure-arity-includes? (keyword-procedure-proc p) n) (let-values ([(missing-kw extra-kw) (check-kw-args p kws)]) (and (not missing-kw) (not extra-kw)))) ;; Ok: (keyword-procedure-proc p) ;; Not ok, so far: (let ([p2 (if (keyword-procedure? p) #f (if (procedure? p) (or (procedure-extract-target p) (and (new-procedure? p) 'method)) #f))]) (if p2 ;; Maybe the target is ok: (if (eq? p2 'method) ;; Build wrapper method: (let ([p3 (keyword-procedure-extract/method kws (add1 n) (new-procedure-ref p) (add1 method-n))]) (lambda (kws kw-args . args) (apply p3 kws kw-args (cons p args)))) ;; Recur: (keyword-procedure-extract/method kws n p2 method-n)) ;; Not ok, period: (lambda (kws kw-args . args) (let-values ([(missing-kw extra-kw) (if (keyword-procedure? p) (check-kw-args p kws) (values #f (car kws)))] [(n) (let ([method-n (+ method-n (if (or (keyword-method? p) (okm? p)) 1 0))]) (if (n . >= . method-n) (- n method-n) n))]) (let ([args-str (if (and (null? args) (null? kws)) "no arguments supplied" ;; Hack to format arguments: (with-handlers ([exn:fail? (lambda (exn) (format "arguments were: ~a" (cadr (regexp-match #rx"other arguments were: (.*)$" (exn-message exn)))))]) (apply raise-type-error 'x "x" 0 'x (append args (apply append (map list kws kw-args))))))]) (raise (make-exn:fail:contract (if extra-kw (if (keyword-procedure? p) (format (string-append "procedure application: procedure: ~e;" " does not expect an argument with keyword ~a; ~a") p extra-kw args-str) (format (string-append "procedure application: expected a procedure that" " accepts keyword arguments, given ~e; ~a") p args-str)) (if missing-kw (format (string-append "procedure application: procedure: ~e; requires" " an argument with keyword ~a, not supplied; ~a") p missing-kw args-str) (format (string-append "procedure application: no case matching ~a non-keyword" " argument~a for: ~e; ~a") (- n 2) (if (= 1 (- n 2)) "" "s") p args-str))) (current-continuation-marks)))))))))) (define (keyword-procedure-extract kws n p) (keyword-procedure-extract/method kws n p 0)) ;; setting procedure arity (define (procedure-reduce-keyword-arity proc arity req-kw allowed-kw) (let ([plain-proc (procedure-reduce-arity (if (okp? proc) (okp-ref proc 0) proc) arity)]) (define (sorted? kws) (let loop ([kws kws]) (cond [(null? kws) #t] [(null? (cdr kws)) #t] [(keyword