#lang scheme/base
(require scheme/match
         (only-in srfi/1 zip unzip2)
         "base.ss"
         "exn.ss")
(define keyword-apply*
  (match-lambda*
    [(list (? procedure? proc) args ... rest)
     
                                                  (define (expand args key-accum val-accum arg-accum)
       (match args
                  [(list)
          (finish key-accum
                  val-accum
                  arg-accum)]
                  [(list arg)
          (if (keyword? rest)
              (raise-exn exn:fail:contract
                (format "keyword does not have a value: ~s" rest))
              (expand null
                      key-accum
                      val-accum
                      (cons arg arg-accum)))]
                  [(list-rest (? keyword? key) val rest)
          (if (keyword? val)
              (raise-exn exn:fail:contract
                (format "keyword does not have a value: ~s" key))
              (expand rest
                      (cons key key-accum)
                      (cons val val-accum)
                      arg-accum))]
                  [(list-rest arg rest)
          (expand rest
                  key-accum
                  val-accum
                  (cons arg arg-accum))]))
     
          (define (finish keys vals args)
       (define-values (sorted-keys sorted-vals)
         (unzip2 (sort (zip keys vals)
                       (lambda (kvp1 kvp2)
                         (keyword<? (car kvp1) (car kvp2))))))
       (keyword-apply proc
                      sorted-keys
                      sorted-vals
                      (reverse args)))
     
     (if (or (null? rest) (pair? rest))
         (expand (append args rest) null null null)
         (raise-exn exn:fail:contract
           (format "final argument must be a list: ~s" rest)))]))
(provide/contract
 [keyword-apply* (->* (procedure? any/c) () #:rest any/c any)])