#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)])