diff --git a/collects/mzlib/kw.ss b/collects/mzlib/kw.ss index 6a8e187867..752141f1cf 100644 --- a/collects/mzlib/kw.ss +++ b/collects/mzlib/kw.ss @@ -29,6 +29,8 @@ (apply append (map (lambda (ks) (list (cadr ks) (caddr ks))) processed-keyword-specs)))) +(define true (list 'true)) ; used for flag values + (provide lambda/kw) (define-syntax (lambda/kw stx) ;; -------------------------------------------------------------------------- @@ -83,9 +85,23 @@ (list #'var #'key #'default)] [(var default) (identifier? #'var) (list #'var (key #'var) #'default)] [(var) (identifier? #'var) (list #'var (key #'var) #'#f)] - [var (identifier? #'var) (list #'var (key #'var) #'#f)] + [var (identifier? #'var) (list #'var (key #'var) #'#f)] [var (serror #'var "not a valid ~a spec" #:key)])) ;; -------------------------------------------------------------------------- + ;; process a flag argument spec, returns ( ) + ;; so it can be used like keys + (define (process-flag k) + (define (key var) + (datum->syntax-object + k (string->keyword (symbol->string (syntax-e var))) k k)) + (syntax-case k () + [(var key) + (and (identifier? #'var) (keyword? (syntax-e #'key))) + (list #'var #'key #'#f)] + [(var) (identifier? #'var) (list #'var (key #'var) #'#f)] + [var (identifier? #'var) (list #'var (key #'var) #'#f)] + [var (serror #'var "not a valid ~a spec" #:flag)])) + ;; -------------------------------------------------------------------------- ;; helpers for process-vars (define ((process-mode modes rests) processed-spec) (let ([allow (memq (cadr processed-spec) modes)] @@ -125,27 +141,28 @@ #'(getter rest key default))) ;; -------------------------------------------------------------------------- ;; test variables - (define (process-vars vars opts keys0 rests modes . only-vars?) + (define (process-vars vars opts keys0 flags rests modes . only-vars?) (define (gensym x) (car (generate-temporaries (list x)))) (let*-values ([(only-vars?) (and (pair? only-vars?) (car only-vars?))] - [(opts keys0) (values (map process-opt opts) (map process-key keys0))] + [(opts) (map process-opt opts)] + [(keys0) (map process-key keys0)] + [(flags) (map process-flag flags)] [(rest body all-keys other-keys other-keys+body) (apply values (map (lambda (k) (cond [(assq k rests) => cdr] [else #f])) rest-like-kwds))] - [(rest* body* other-keys*) - (values (or rest (gensym #'rest)) - (if (and body (identifier? body)) body (gensym #'body)) - (or other-keys (gensym #'other-keys)))] + [(rest*) (or rest (gensym #'rest))] + [(body*) (if (and body (identifier? body)) body (gensym #'body))] + [(other-keys*) (or other-keys (gensym #'other-keys))] [(other-keys-mode duplicate-keys-mode body-mode anything-mode) (apply values (map (process-mode modes rests) processed-keyword-specs))] - ;; turn ( ) keys to ( ) + ;; turn ( ) keys to ( ) [(keys) (with-syntax ([rst rest*]) - (let loop ([ks keys0] [r '()] + (let loop ([ks (append keys0 flags)] [r '()] [known-vars (append vars (map car opts))]) (if (null? ks) (reverse! r) @@ -169,10 +186,11 @@ => (lambda (d) (serror d "not an identifier"))] [(check-duplicate-identifier all-ids) => (lambda (d) (serror d "duplicate argument name"))] - [else (values vars opts keys rest rest* body body* all-keys - other-keys other-keys* other-keys+body - other-keys-mode duplicate-keys-mode body-mode - anything-mode (map cadr keys0))]))) + [else (values + vars opts keys (map cadr flags) rest rest* body body* + all-keys other-keys other-keys* other-keys+body + other-keys-mode duplicate-keys-mode body-mode anything-mode + (append (map cadr keys0) (map cadr flags)))]))) ;; -------------------------------------------------------------------------- ;; parses formals, returns list of normal vars, optional var specs, key var ;; specs, an alist of rest-like kw+vars, and a mode for allowing other keys @@ -181,28 +199,36 @@ (let* ([formals (split-by-keywords formals)] [vars (car formals)] [formals (cdr formals)] - [pop-formals - (lambda (key) - (if (and (pair? formals) (eq? key (syntax-e* (caar formals)))) - (begin0 (cdar formals) (set! formals (cdr formals))) - '()))] - [opts (pop-formals #:optional)] - [keys (pop-formals #:key)]) + [opts '()] + [keys '()] + [flags '()]) + (when (and (pair? formals) (eq? #:optional (syntax-e* (caar formals)))) + (set! opts (cdar formals)) (set! formals (cdr formals))) + (let loop ([last #f]) + (let* ([k-stx (and (pair? formals) (caar formals))] + [k (and k-stx (syntax-e* k-stx))]) + (when (and k (eq? k last)) (serror k-stx "two ~s sections" k)) + (case k + [(#:key) (set! keys (append! keys (cdar formals))) + (set! formals (cdr formals)) (loop k)] + [(#:flag) (set! flags (append! flags (cdar formals))) + (set! formals (cdr formals)) (loop k)] + #| else continue below |#))) ;; now get all rest-like vars and modes (let loop ([formals formals] [rests '()] [modes '()]) (if (null? formals) - (apply process-vars vars opts keys rests modes only-vars?) + (apply process-vars vars opts keys flags rests modes only-vars?) (let* ([k-stx (caar formals)] - [k (syntax-e* k-stx)]) - (cond [(memq k '(#:optional #:key)) + [k (syntax-e* k-stx)]) + (cond [(memq k '(#:optional #:key #:flag)) (serror k-stx "misplaced ~a" k)] [(memq k mode-keywords) (cond #; ;(*) ;; don't throw an error here, it still fine if used with ;; #:allow-other-keys (explicit or implicit), also below - [(null? keys) - (serror k-stx "cannot use without #:key arguments")] + [(and (null? keys) (null? flags)) + (serror k-stx "cannot use without #:key/#:flag arguments")] [(pair? (cdar formals)) (serror (cadar formals) "identifier following mode keyword ~a" k)] @@ -219,7 +245,7 @@ ;; same as above: don't throw an error here, still fine if ;; used with #:allow-other-keys (explicit or implicit) [(and (null? keys) (not (eq? #:rest k))) - (serror k-stx "cannot use without #:key arguments")] + (serror k-stx "cannot use without #:key/#:flag arguments")] [else (loop (cdr formals) (cons (cons k (cadar formals)) rests) modes)])))))) @@ -232,6 +258,7 @@ (define-values (vars ; plain variables opts ; optionals, each is (id default) keys ; keywords, each is (id key default) + flags ; flag keyword syntaxes (args are part of keys) rest ; rest variable (no optionals) rest* ; always an id body ; rest after all keyword-vals (id or formals) @@ -341,14 +368,15 @@ expr) #'expr)]) (if (and allow-anything? (not body) - (not other-keys+body) (not all-keys) (not other-keys)) + (not other-keys+body) (not all-keys) (not other-keys) + (null? flags)) ;; allowing anything and don't need special rests, so no loop #'expr ;; normal code #`(let loop loop-vars (if (and (pair? body*) (keyword? (car body*)) #,@(if allow-anything? #'((pair? (cdr body*))) '())) - #,(if allow-anything? ; already checker pair? above + #,(if allow-anything? ; already checked pair? above #'next-loop #'(if (pair? (cdr body*)) next-loop @@ -369,18 +397,36 @@ (error* 'name "expecting a ~s keyword got: ~e" 'keywords (car body*)))))))))) ;; ------------------------------------------------------------------------ + ;; generates the loop that turns flags to #t's + (define (make-flags-body) ; called only when there are flags + (with-syntax ([flags flags] [rest* rest*]) + #'(let loop ([xs rest*]) + (when (and (pair? xs) (keyword? (car xs))) + (if (memq (car xs) 'flags) + (if (null? (cdr xs)) + (set-cdr! xs (list true)) + (begin (unless (eq? true (cadr xs)) + (set-cdr! xs (cons true (cdr xs)))) + (loop (cddr xs)))) + (when (pair? (cdr xs)) (loop (cddr xs)))))))) + ;; ------------------------------------------------------------------------ ;; generates the part of the body that deals with rest-related stuff (define (make-keys-body expr) - (with-syntax ([body (make-rest-body expr)] [keys keys]) - #'(let* keys body))) + (let ([kb (with-syntax ([body (make-rest-body expr)] [keys keys]) + #'(let* keys body))]) + (if (null? flags) + kb + (with-syntax ([keys-body kb] [flag-tweaks (make-flags-body)]) + #'(begin flag-tweaks keys-body))))) ;; ------------------------------------------------------------------------ ;; more sanity tests (see commented code above -- search for "(*)") (let ([r (or all-keys other-keys other-keys+body body rest)]) (when (and (not allow-other-keys?) (null? keys)) - (when r (serror r "cannot use without #:key or #:allow-other-keys")) + (when r + (serror r "cannot use without #:key, #:flag, or #:allow-other-keys")) (when allow-duplicate-keys? (serror #f (string-append "cannot allow duplicate keys without" - " #:key or #:allow-other-keys")))) + " #:key, #:flag, or #:allow-other-keys")))) (when (and allow-other-keys? (null? keys) (not r)) (serror #f "cannout allow other keys without using them in some way"))) ;; ------------------------------------------------------------------------