racket/collects/plot/common/contract-doc.rkt
2011-10-07 13:09:27 -06:00

200 lines
8.8 KiB
Racket

#lang racket/base
;; Definitions with contracts and contract documentation.
(require racket/contract
(for-syntax racket/base racket/list syntax/parse
"serialize-syntax.rkt")
(prefix-in s. scribble/manual)
(prefix-in s. scribble/core)
(prefix-in s. scribble/html-properties))
(provide defproc defparam defcontract doc-apply)
(begin-for-syntax
(struct proc+doc (proc-transformer doc-transformer)
#:property prop:procedure (λ (p stx) ((proc+doc-proc-transformer p) stx)))
(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]))
)
(define-for-syntax (make-proc+doc id-stx doc-transformer)
(proc+doc
(λ (stx)
(syntax-case stx ()
[(_ . args) (quasisyntax/loc stx (#,id-stx . args))]
[_ (quasisyntax/loc stx #,id-stx)]))
doc-transformer))
;; Applies the documentation transformer (use within a scribble/manual module)
(define-syntax (doc-apply stx)
(define (error) (raise-syntax-error 'doc-apply "no associated doc transformer" stx))
(syntax-parse stx
[(_ name:id . pre-flows)
(define p (syntax-local-value #'name error))
(when (not (proc+doc? p)) (error))
((proc+doc-doc-transformer p) (syntax/loc stx (name . pre-flows)))]))
;; 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)))
(define-for-syntax (make-value-name id-stx)
(datum->syntax #f (syntax->datum id-stx)))
(define-for-syntax (make-doc-name ctx id-stx)
(datum->syntax ctx (syntax->datum id-stx)))
;; ===================================================================================================
;; define-with-contract+doc forms
;; Define a procedure
(define-syntax (defproc stx)
(syntax-parse stx
[(_ (name:id arg:argument-spec ...) result:expr body ...+)
(define arg-list (syntax->list #'(arg ...)))
(with-syntax ([value-name (make-value-name #'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/contract (value-name new-arg ...) (->* (req-contract ...) (opt-contract ...)
result)
body ...)
(define-syntax name
(make-proc+doc
#'value-name
(λ (doc-stx)
(syntax-case doc-stx ()
[(ctx . pre-flows)
(with-syntax ([doc-name (make-doc-name #'ctx #'name)]
[doc-args (unserialize-syntax #'ctx 'serialized-args)]
[doc-result (unserialize-syntax #'ctx 'serialized-result)])
#'(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 ([value-name (make-value-name #'name)]
[serialized-contract (serialize-syntax #'contract)]
[serialized-default (serialize-syntax #'default)])
(syntax/loc stx
(begin
(define/contract value-name (parameter/c contract) (make-parameter default))
(define-syntax name
(make-proc+doc
#'value-name
(λ (doc-stx)
(syntax-case doc-stx ()
[(ctx . pre-flows)
(with-syntax ([doc-name (make-doc-name #'ctx #'name)]
[doc-arg (make-doc-name #'ctx #'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)
(with-syntax ([value-name (make-value-name #'name)]
[serialized-value (serialize-syntax #'value)])
(syntax/loc stx
(begin
(define value-name value)
(define-syntax name
(make-proc+doc
#'value-name
(λ (doc-stx)
(syntax-case doc-stx ()
[(ctx . pre-flows)
(with-syntax ([doc-name (make-doc-name #'ctx #'name)]
[doc-contract? (make-doc-name #'ctx #'contract?)]
[doc-value (unserialize-syntax #'ctx 'serialized-value)])
#'(def/value
(s.defthing doc-name doc-contract?)
(s.racketblock doc-value)
. pre-flows))])))))))]
[(_ (name:id arg:argument-spec ...) body)
(define arg-list (syntax->list #'(arg ...)))
(with-syntax ([value-name (make-value-name #'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-body (serialize-syntax #'body)])
(syntax/loc stx
(begin
(define/contract (value-name new-arg ...) (->* (req-contract ...) (opt-contract ...)
contract?)
body)
(define-syntax name
(make-proc+doc
#'value-name
(λ (doc-stx)
(syntax-case doc-stx (doc)
[(ctx . pre-flows)
(with-syntax ([doc-name (make-doc-name #'ctx #'name)]
[doc-contract? (make-doc-name #'ctx #'contract?)]
[doc-args (unserialize-syntax #'ctx 'serialized-args)]
[doc-body (unserialize-syntax #'ctx 'serialized-body)])
#'(def/value
(s.defproc (doc-name . doc-args) doc-contract?)
(s.racketblock doc-body)
. pre-flows))])))))))]))