#lang type-expander

(provide :contract→type
         (rename-out [c→t contract→type]
                     [c→t contract->type]
                     [:contract→type :contract->type]))
(require (prefix-in c: (combine-in racket/base racket/contract/base))
         (for-syntax racket/base
                     syntax/parse
                     syntax/parse/experimental/template
                     type-expander/expander))

(begin-for-syntax
  (define-syntax-class arrow
    (pattern {~or {~literal ->} {~literal →} {~literal c:->}}))
  (define-syntax-class arrow*
    (pattern {~or {~literal ->*} {~literal c:->*}})))

(define-type-expander c→t
  (syntax-parser
    [(_ ({~literal c:or/c} alt ...)) #'(U (c→t alt) ...)]
    [(_ ({~literal c:and/c} alt ...)) #'(∩ (c→t alt) ...)]
    [(_ ({~literal c:listof} c)) #'(Listof (c→t c))]
    [(_ ({~literal c:list/c} c ...)) #'(List (c→t c) ...)]
    [(_ ({~literal c:*list/c} prefix suffix ...))
     #'(Rec R (U (Pairof (c→t prefix) R)
                 (List (c→t suffix) ...)))]
    [(_ ({~literal c:vectorof} c)) #'(Vectorof (c→t c))]
    [(_ ({~literal c:vector/c} c ...)) #'(Vector (c→t c) ...)]
    [(_ ({~literal c:cons/c} a d)) #'(Pairof (c→t a) (c→t d))]
    [(_ {~literal c:number?}) #'Number]
    [(_ {~literal c:integer?}) #'Integer]
    [(_ {~literal c:string?}) #'String]
    [(_ {~literal c:symbol?}) #'Symbol]
    [(_ {~literal c:char?}) #'Char]
    [(_ {~literal c:boolean?}) #'Boolean]
    [(_ {~literal c:bytes?}) #'Bytes]
    [(_ {~literal c:void?}) #'Void]
    [(_ {~literal c:null?}) #'Null]
    [(_ {~literal c:empty?}) #'Null]
    [(_ {~literal c:list?}) #'(Listof Any)]
    [(_ {~literal c:exact-nonnegative-integer?}) #'Exact-Nonnegative-Integer]
    [(_ {~literal c:exact-positive-integer?}) #'Exact-Positive-Integer]
    [(_ ({~literal c:syntax/c} τ)) #'(Syntaxof (c→t τ))]
    [(_ ({~literal c:parameter/c} in)) #'(Parameterof (c→t in))]
    [(_ ({~literal c:parameter/c} in out)) #'(Parameterof (c→t in) (c→t out))]
    [(_ ({~literal c:promise/c} τ)) #'(Promise (c→t τ))]
    [(_ ({~literal c:suggest/c} τ)) #'(c→t τ)]
    [(_ ({~literal c:flat-rec-contract} R alt ...))
     #`(Rec R (U (c→t alt) ...))]
    [(_ (a:arrow {~seq {~optional kw:keyword}
                       {~and arg {~not {~literal ...}}}}
                 ...
                 rest {~and {~literal ...} ooo}
                 result))
     #:with rest-kw (datum->syntax #'here '#:rest #'ooo)
     #:with a* (datum->syntax #'here '->* #'a)
     (template (a* ((?@ (?? kw) (c→t arg)) ...)
                   rest-kw (c→t rest)
                   (c→t result)))]
    [(_ (a:arrow {~seq {~optional kw:keyword}
                       {~and arg {~not {~literal ...}}}}
                 ...
                 result))
     (template (a (?@ (?? kw) (c→t arg)) ... (c→t result)))]
    [(_ (a*:arrow* ({~seq {~optional mandatory-kw:keyword}
                          mandatory-arg}
                    ...)
                   {~optional
                    {~and opt
                          ({~seq {~optional optional-kw:keyword}
                                 optional-arg}
                           ...)}}
                   {~optional {~seq #:rest ({~literal c:listof} rest)}}
                   result))
     (quasitemplate (a* ((?@ (?? mandatory-kw) (c→t mandatory-arg)) ...)
                        #,@(if (attribute opt)
                               (template
                                {((?@ (?? optional-kw) (c→t optional-arg))
                                  ...)})
                               #'{})
                        (?? (?@ #:rest (c→t rest)))
                        (c→t result)))]
    [(_ {~literal c:any}) #'AnyValues]
    [(_ ({~literal c:values} v ...)) #'(Values (c→t v) ...)]
    [(_ {~and τ ({~literal quote} _)}) #'τ]
    [(_ {~and τ {~or :number :str :char :boolean}}) #''τ]
    [(_ {~and τ}) #:when (bytes? (syntax-e #'τ)) #''τ]
    [(_ {~and τ}) #:when (regexp? (syntax-e #'τ)) #''τ]
    [(_ {~and τ}) #:when (byte-regexp? (syntax-e #'τ)) #''τ]
    [(_ {~and τ ({~literal quasiquote} _)}) #'τ]
    [(_ ({~literal unquote} τ)) #'τ]
    [(_ v:id)
     ;; TODO: this is a terrible implementation. type-expander should provide
     ;; a way to attach information to an identifier, so that we can know that
     ;; v is a variable bound by flat-rec-contract.
     #'v]
    [(_ c) (raise-syntax-error
            'contract→type
            (string-append
             "I cannot convert this contract to a type automatically."
             " Please fill in an issue at"
             " https://github.com/jsmaniac/type-expander/issues if the"
             " translation can easily be done automatically, or do the"
             " translation manually otherwise. ")
            #'c)]))

(define-syntax (:contract→type stx)
  (syntax-case stx ()
    [(_ c) #`(writeln '#,(expand-type #`(c→t c)))]))