#lang racket/base
(require syntax/parse)
(define unspecified-stx-symbol '<unspecified>)
(define none-stx-symbol '<none>)
(provide unspecified-stx)
(define unspecified-stx
(read-syntax 'mcfly-unspecified
(let ((in (open-input-string (symbol->string unspecified-stx-symbol))))
(port-count-lines! in)
(set-port-next-location! in #f #f #f)
in)))
(provide none-stx)
(define none-stx
(read-syntax 'mcfly-none
(let ((in (open-input-string (symbol->string none-stx-symbol))))
(port-count-lines! in)
(set-port-next-location! in #f #f #f)
in)))
(define (unspecified-stx? x)
(and (syntax? x)
(eq? 'mcfly-unspecified (syntax-source x))))
(define (none-stx? x)
(and (syntax? x)
(eq? 'mcfly-none (syntax-source x))))
(define (%split-heads-and-tails-and-nonpairs things)
(let loop ((things (reverse things))
(heads '())
(tails '())
(nonpairs '()))
(if (null? things)
(values heads tails nonpairs)
(let ((thing (car things)))
(cond ((null? thing) (loop (cdr things)
heads
tails
nonpairs))
((pair? thing) (loop (cdr things)
(cons (car thing) heads)
(cons (cdr thing) tails)
nonpairs))
(else (loop (cdr things)
heads
tails
(cons thing nonpairs))))))))
(provide unify-specs)
(define unify-specs
(letrec
((do-spec-tails
(lambda (ctxt pair-spec-tails)
(datum->syntax ctxt
(do-spec-tails/return-pair
ctxt
pair-spec-tails))))
(do-spec-tails/return-pair
(lambda (ctxt pair-spec-tails)
(let*-values
(((heads tails nonpairs)
(%split-heads-and-tails-and-nonpairs pair-spec-tails))
((head-stx)
(begin
(let loop-heads ((heads heads)
(best #f))
(if (null? heads)
best
(let ((this-head (car heads)))
(cond ((null? this-head)
(loop-heads (cdr heads) best))
((unspecified-stx? this-head)
(loop-heads (cdr heads) (or best this-head)))
(else (let ((this-head-e (syntax-e this-head)))
(cond
((pair? this-head-e)
(do-spec-tails ctxt
(cons this-head-e
(let loop-gather-remaining-pair-heads ((r-heads heads))
(if (null? r-heads)
'()
(let ((this-r-head (car r-heads)))
(if (unspecified-stx? this-r-head)
(loop-gather-remaining-pair-heads (cdr r-heads))
(let ((this-r-head-e (syntax-e this-r-head)))
(if (pair? this-r-head-e)
(cons this-r-head-e
(loop-gather-remaining-pair-heads (cdr r-heads)))
(loop-gather-remaining-pair-heads (cdr r-heads)))))))))))
(else (loop-heads (cdr heads)
(if (or (not best) (unspecified-stx? best))
this-head
best))))))))))))
((head-stx)
(if (and head-stx (not (unspecified-stx? head-stx)))
head-stx
(let loop ((nonpairs nonpairs)
(best #f))
(if (null? nonpairs)
best
(let ((this-nonpair (car nonpairs)))
(if (unspecified-stx? this-nonpair)
(loop (cdr nonpairs) (or best this-nonpair))
this-nonpair
)))))))
(if (null? tails)
(if head-stx
(cons head-stx '())
'())
(cons (or head-stx unspecified-stx)
(do-spec-tails/return-pair ctxt tails)))))))
(lambda (ctxt specs)
(car
(do-spec-tails/return-pair ctxt (map list specs))))))
(define (%mcfly:spec->spec-with-unspecifieds-replaced+replacement-count spec)
(let* ((unspecified-count 0)
(spec (let loop ((spec spec))
(cond ((unspecified-stx? spec)
(begin0 (quasisyntax/loc spec
#,(string->symbol (format "unspecified-~A"
unspecified-count)))
(set! unspecified-count (+ 1 unspecified-count))))
((pair? spec) (map loop spec))
(else (let ((spec-e (syntax-e spec)))
(if (pair? spec-e)
(loop spec-e)
spec)))))))
(values spec
unspecified-count)))
(provide unify-tagged-specs-for-procedure)
(define (unify-tagged-specs-for-procedure ctxt tagged-specs reverse-warnings)
(let*-values (((specs) (map cdr tagged-specs))
((unified-spec) (unify-specs ctxt specs))
((unified-spec count) (%mcfly:spec->spec-with-unspecifieds-replaced+replacement-count
unified-spec))
((proto-stx result-stx) (procedure-spec->scribble-prototype+result
unified-spec)))
(values proto-stx
result-stx
(if (zero? count)
reverse-warnings
(cons #`("Procedure information had unspecified values: "
(tt (racketresult #,unified-spec)))
reverse-warnings)))))
(define-splicing-syntax-class nonrest-lambda-arg
#:attributes (parsed)
#:description "non-rest lambda argument"
(pattern NAME:id
#:with parsed #`(argument #f NAME #,unspecified-stx #,none-stx))
(pattern (NAME:id DEFAULT:expr)
#:with parsed #`(argument #f NAME #,unspecified-stx DEFAULT))
(pattern (~seq KEYWORD:keyword NAME:id)
#:with parsed #`(argument KEYWORD NAME #,unspecified-stx #,none-stx))
(pattern (~seq KEYWORD:keyword (NAME:id DEFAULT:expr))
#:with parsed #`(argument KEYWORD NAME #,unspecified-stx DEFAULT))
(pattern (~seq (~datum ...))
#:with parsed #`(ellipses-argument))
(pattern (~seq (~datum ...+))
#:with parsed #`(ellipses-plus-argument)))
(provide lambda-formal-stx->spec)
(define (lambda-formal-stx->spec name-stx stx)
(syntax-parse stx
(NAME:id
#`(procedure (prototype #,name-stx
((argument #f NAME #,unspecified-stx #,none-stx)
(ellipses-argument)))
#,unspecified-stx))
((NONREST-ARG:nonrest-lambda-arg ...)
#`(procedure (prototype #,name-stx
(NONREST-ARG.parsed ...))
#,unspecified-stx))
((NONREST-ARG:nonrest-lambda-arg ...+ . RESTNAME:id)
#`(procedure (prototype #,name-stx
(NONREST-ARG.parsed ...
(argument #f
RESTNAME
#,unspecified-stx
#,none-stx)
(ellipses-argument)))
#,unspecified-stx))
((NONREST-ARG:nonrest-lambda-arg ...+ . (RESTNAME:id DEFAULT))
#`(procedure (prototype #,name-stx
(NONREST-ARG.parsed ...
(argument #f
RESTNAME
#,unspecified-stx
DEFAULT)
(ellipses-argument)))
#,unspecified-stx))
(ELSE
(error 'lambda-formal-stx->spec
"unrecognized lambda argument syntax: ~S"
stx))))
(provide contract-stx->spec-or-false)
(define (contract-stx->spec-or-false id-stx stx)
(syntax-parse stx
(((~datum ->) ARG ... RESULT)
(quasisyntax/loc stx
(procedure (prototype #,id-stx
((argument #f #,unspecified-stx ARG #,unspecified-stx) ...))
RESULT)))
(ELSE #f)))
(provide procedure-spec+body->scribble-defproc)
(define (procedure-spec+body->scribble-defproc spec-stx body-stx)
(syntax-parse spec-stx
(((~datum procedure) PROTOTYPE RESULT)
#`(defproc #,(prototype-spec->scribble-defproc-prototype #'PROTOTYPE)
RESULT #,@body-stx))))
(provide procedure-spec->scribble-prototype+result)
(define (procedure-spec->scribble-prototype+result spec-stx)
(syntax-parse spec-stx
(((~datum procedure) PROTOTYPE RESULT)
(values (prototype-spec->scribble-defproc-prototype #'PROTOTYPE)
#'RESULT))))
(define (prototype-spec->scribble-defproc-prototype prototype-stx)
(syntax-parse prototype-stx
(((~datum prototype) NAME:id (ARGUMENT ...))
#`(NAME #,@(map argument-spec->scribble-defproc-prototype-argument
(syntax->list #'(ARGUMENT ...)))))
(((~datum prototype) (PROTOTYPE ...) (ARGUMENT ...))
#`(#,(prototype-spec->scribble-defproc-prototype #'(PROTOTYPE ...))
#,(map argument-spec->scribble-defproc-prototype-argument
(syntax->list #'(ARGUMENT ...)))))))
(define (argument-spec->scribble-defproc-prototype-argument argument-stx)
(syntax-parse argument-stx
(((~datum argument) #f NAME:id CONTRACT DEFAULT)
(if (none-stx? #'DEFAULT)
#'(NAME CONTRACT)
#'(NAME CONTRACT DEFAULT)))
(((~datum argument) KEYWORD:keyword NAME:id CONTRACT DEFAULT)
(if (none-stx? #'DEFAULT)
#'(KEYWORD NAME CONTRACT)
#'(KEYWORD NAME CONTRACT DEFAULT)))
(((~datum argument) X ...)
(error 'argument-spec->scribble-defproc-prototype-argument
"could not parse ~S"
(syntax->datum argument-stx)))
(((~datum ellipses-argument))
#'(... ...))
(((~datum ellipses-plus-argument))
#'(... ...+))))