racket/collects/plot/common/contract-doc.rkt
Neil Toronto e90ec4b69f Added unstable/latent-contract
Reorganized contracts
Started exposing customization API in plot/utils
Now dog-fooding customization API in earnest
2011-11-10 12:59:41 -07:00

208 lines
9.7 KiB
Racket

#lang racket/base
;; Definitions with contracts and contract documentation.
(require racket/contract unstable/latent-contract racket/provide
(for-syntax racket/base racket/list racket/syntax syntax/parse racket/provide-transform
"serialize-syntax.rkt")
(prefix-in s. scribble/manual)
(prefix-in s. scribble/core)
(prefix-in s. scribble/html-properties))
(provide defthing defproc defparam defcontract
only-doc-out doc-apply)
(begin-for-syntax
(define-syntax-class argument-spec
#:description "argument specification"
(pattern [name:id contract:expr])
(pattern [name:id contract:expr default:expr])
(pattern [kw:keyword name:id contract:expr])
(pattern [kw:keyword name:id contract:expr default:expr])))
;; A define-with-value form for scribble documentation
(define (def/value def val . pre-flows)
(apply s.nested (s.tabular #:style def/value-table-style
(list (list (s.nested def) 'cont)
(list "=" val)))
pre-flows))
(define def/value-table-style
(s.style 'boxed
(list (s.table-columns
(list (s.style 'plain (list 'top (s.attributes '((width . "0%")))))
(s.style 'plain (list 'top 'left (s.attributes '((width . "100%"))))))))))
;; ===================================================================================================
;; Helpers
(define-for-syntax (get-required-contract arg-stx)
(syntax-parse arg-stx
[(name:id contract:expr) (list #'contract)]
[(kw:keyword name:id contract:expr) (list #'kw #'contract)]
[_ empty]))
(define-for-syntax (get-optional-contract arg-stx)
(syntax-parse arg-stx
[(name:id contract:expr default:expr) (list #'contract)]
[(kw:keyword name:id contract:expr default:expr) (list #'kw #'contract)]
[_ empty]))
(define-for-syntax (remove-contract arg-stx)
(syntax-parse arg-stx
[(name:id contract:expr) (list #'name)]
[(name:id contract:expr default:expr) (list #'(name default))]
[(kw:keyword name:id contract:expr) (list #'kw #'name)]
[(kw:keyword name:id contract:expr default:expr) (list #'kw #'(name default))]))
(define-for-syntax (parameter-name->arg-name name-stx)
(define name-str (symbol->string (syntax->datum name-stx)))
(define arg-name-str
(cond [(regexp-match #rx".*-(.*)$" name-str) => (λ (m) (last m))]
[(regexp-match #rx"^$" name-str) => (λ (m) "value")]
[else (substring name-str 0 1)]))
(datum->syntax name-stx (string->symbol arg-name-str)))
;; ===================================================================================================
;; Forms to define things with a contract and documentation
;; Define a thing, optionally documenting the value of the thing
(define-syntax (defthing stx)
(syntax-parse stx
[(_ name:id contract:expr #:document-value value:expr)
(with-syntax ([name:doc (format-id #'name "~a:doc" #'name)]
[serialized-contract (serialize-syntax #'contract)]
[serialized-value (serialize-syntax #'value)])
(syntax/loc stx
(begin
(define/latent-contract name contract value)
(define-syntax (name:doc doc-stx)
(syntax-case doc-stx ()
[(ctx . pre-flows)
(with-syntax ([doc-name (datum->syntax #'ctx (syntax-e #'name))]
[doc-contract (unserialize-syntax #'ctx 'serialized-contract)]
[doc-value (unserialize-syntax #'ctx 'serialized-value)])
(syntax/loc doc-stx
(def/value
(s.defthing doc-name doc-contract)
(s.racketblock doc-value)
. pre-flows)))])))))]
[(_ name:id contract:expr value:expr)
(with-syntax ([name:doc (format-id #'name "~a:doc" #'name)]
[serialized-contract (serialize-syntax #'contract)])
(syntax/loc stx
(begin
(define/latent-contract name contract value)
(define-syntax (name:doc doc-stx)
(syntax-case doc-stx ()
[(ctx . pre-flows)
(with-syntax ([doc-name (datum->syntax #'ctx (syntax-e #'name))]
[doc-contract (unserialize-syntax #'ctx 'serialized-contract)])
(syntax/loc doc-stx
(s.defthing doc-name doc-contract . pre-flows)))])))))]))
;; Define a procedure
(define-syntax (defproc stx)
(syntax-parse stx
[(_ (name:id arg:argument-spec ...) result:expr #:document-body body ...+)
(define arg-list (syntax->list #'(arg ...)))
(with-syntax ([name:doc (format-id #'name "~a:doc" #'name)]
[(new-arg ...) (append* (map remove-contract arg-list))]
[(req-contract ...) (append* (map get-required-contract arg-list))]
[(opt-contract ...) (append* (map get-optional-contract arg-list))]
[serialized-args (serialize-syntax #'(arg ...))]
[serialized-result (serialize-syntax #'result)]
[serialized-body (serialize-syntax #'(body ...))])
(syntax/loc stx
(begin
(define/latent-contract (name new-arg ...) (->* (req-contract ...) (opt-contract ...)
result)
body ...)
(define-syntax (name:doc doc-stx)
(syntax-case doc-stx ()
[(ctx . pre-flows)
(with-syntax ([doc-name (datum->syntax #'ctx (syntax-e #'name))]
[doc-args (unserialize-syntax #'ctx 'serialized-args)]
[doc-result (unserialize-syntax #'ctx 'serialized-result)]
[doc-body (unserialize-syntax #'ctx 'serialized-body)])
(syntax/loc doc-stx
(def/value
(s.defproc (doc-name . doc-args) doc-result)
(s.racketblock . doc-body)
. pre-flows)))])))))]
[(_ (name:id arg:argument-spec ...) result:expr body ...+)
(define arg-list (syntax->list #'(arg ...)))
(with-syntax ([name:doc (format-id #'name "~a:doc" #'name)]
[(new-arg ...) (append* (map remove-contract arg-list))]
[(req-contract ...) (append* (map get-required-contract arg-list))]
[(opt-contract ...) (append* (map get-optional-contract arg-list))]
[serialized-args (serialize-syntax #'(arg ...))]
[serialized-result (serialize-syntax #'result)])
(syntax/loc stx
(begin
(define/latent-contract (name new-arg ...) (->* (req-contract ...) (opt-contract ...)
result)
body ...)
(define-syntax (name:doc doc-stx)
(syntax-case doc-stx ()
[(ctx . pre-flows)
(with-syntax ([doc-name (datum->syntax #'ctx (syntax-e #'name))]
[doc-args (unserialize-syntax #'ctx 'serialized-args)]
[doc-result (unserialize-syntax #'ctx 'serialized-result)])
(syntax/loc doc-stx
(s.defproc (doc-name . doc-args) doc-result . pre-flows)))])))))]))
;; Define a parameter
(define-syntax (defparam stx)
(syntax-parse stx
[(_ name:id arg:id contract:expr default:expr)
(with-syntax ([name:doc (format-id #'name "~a:doc" #'name)]
[serialized-contract (serialize-syntax #'contract)]
[serialized-default (serialize-syntax #'default)])
(syntax/loc stx
(begin
(define/latent-contract name (parameter/c contract) (make-parameter default))
(define-syntax (name:doc doc-stx)
(syntax-case doc-stx ()
[(ctx . pre-flows)
(with-syntax ([doc-name (datum->syntax #'ctx (syntax-e #'name))]
[doc-arg (datum->syntax #'ctx (syntax-e #'arg))]
[doc-contract (unserialize-syntax #'ctx 'serialized-contract)]
[doc-default (unserialize-syntax #'ctx 'serialized-default)])
#'(def/value
(s.defparam doc-name doc-arg doc-contract)
(s.racketblock doc-default)
. pre-flows))])))))]
[(_ name:id contract:expr default:expr)
(quasisyntax/loc stx
(defparam name #,(parameter-name->arg-name #'name) contract default))]))
;; Define a contract or a procedure that returns a contract
(define-syntax (defcontract stx)
(syntax-parse stx
[(_ name:id value:expr)
(syntax/loc stx (defthing name contract? #:document-value value))]
[(_ (name:id arg:argument-spec ...) body)
(syntax/loc stx (defproc (name arg ...) contract? #:document-body body))]))
;; ===================================================================================================
;; Getting documentation
(define-syntax only-doc-out
(make-provide-pre-transformer
(λ (stx modes)
(syntax-case stx ()
[(_ provide-spec)
(pre-expand-export
(syntax/loc stx
(matching-identifiers-out #rx".*:doc$" provide-spec))
modes)]))))
;; Applies the documentation transformer (use within a scribble/manual module)
(define-syntax (doc-apply stx)
(syntax-parse stx
[(_ name:id . pre-flows)
(with-syntax ([name:doc (format-id #'name "~a:doc" #'name)])
(syntax-protect
(syntax/loc stx (name:doc . pre-flows))))]))