macrotypes/sysf.rkt
2014-08-15 15:33:07 -04:00

226 lines
10 KiB
Racket

#lang racket/base
(require
(for-syntax
racket/base syntax/parse syntax/parse/experimental/template
syntax/stx racket/syntax racket/set
"stx-utils.rkt")
(for-meta 2 racket/base syntax/parse)
"typecheck.rkt")
(require
(except-in
"stlc.rkt"
define-type begin letrec #%module-begin #%app λ
check-type-error check-type check-type-and-result check-not-type check-equal?))
(require (prefix-in stlc: (only-in "stlc.rkt" #%app define-type λ begin)))
(provide (all-from-out "stlc.rkt"))
(provide
define-type
(rename-out
[stlc:begin begin]
[letrec/tc letrec]
[module-begin/tc #%module-begin]
[app/tc #%app]
[λ/tc λ]))
;; define-type ----------------------------------------------------------------
(define-syntax (define-type stx)
(syntax-parse stx #:datum-literals (variant)
[(_ (Tycon:id X ...) (variant (Cons:id τ_fld ...) ...))
#:with ((x ...) ...) (stx-map generate-temporaries #'((τ_fld ...) ...))
;; weird, generate-temporaries doesnt automatically produces ids with the right scope
; #:with (Cons-tmp ...) (stx-map (λ (x) (format-id #'Tycon "~a" x)) (generate-temporaries #'(Cons ...)))
; #:when (expand/df/module-ctx
; #'(begin
; (define-syntax (Cons-tmp stx)
; (syntax-parse stx
; [(_ τ (... ...))
; #:with (X ...) #'(τ (... ...))
; #'(→ τ_fld ... (Tycon X ...))])) ...))
#:when (Γ (type-env-extend #'([Cons ( (X ...) ( τ_fld ... (Tycon X ...)))] ...)))
; #:when (Γ (type-env-extend #'([Cons (Cons-tmp (∀ (X ...) (→ τ_fld ... (Tycon X ...))))] ...)))
; #:when (Γ (type-env-extend #'([Cons (→ τ_fld ... Tycon)] ...)))
#'(begin
; (define-syntax (Cons-tmp stx)
; (syntax-parse stx
; [(_ τ (... ...))
; #:with (X ...) #'(τ (... ...))
; #'(→ τ_fld ... (Tycon X ...))])) ...
#;(define-syntax (Tycon stx)
(syntax-parse stx
[(_ τ (... ...))
#:with (X ...) #'(τ (... ...))
#'(variant (Cons τ_fld ...) ...)]))
(struct Cons (x ...) #:transparent) ...)]
[(_ any ...) #'(stlc:define-type any ...)]))
;; lambda ---------------------------------------------------------------------
(define-syntax (λ/tc stx)
(syntax-parse stx #:datum-literals (:)
[(_ τs ([x:id : τ] ...) e ... e_result)
#:when (curly-parens? #'τs)
;; the with-extended-type-env must be outside the expand/df (instead of
;; around just the body) bc ow the parameter will get restored to the old
;; value before the local-expand happens
#:with (lam xs e+ ... e_result+) (with-extended-type-env #'([x τ] ...)
(expand/df #'(λ (x ...) e ... e_result)))
;; manually handle identifiers here
;; - since Racket has no #%var hook, ids didn't get "expanded" in the previous line
;; and thus didn't get a type
;; TODO: can I put this somewhere else where it's more elegant?
#:with (e++ ... e_result++) (with-extended-type-env #'([x τ] ...)
(stx-map
(λ (e) (if (identifier? e) (expand/df e) e))
#'(e+ ... e_result+)))
;; manually handle the implicit begin
#:when (stx-map assert-Unit-type #'(e++ ...))
#:with τ_body (typeof #'e_result++)
( (syntax/loc stx (lam xs e++ ... e_result++))
#'( τs ( τ ... τ_body)))]
[(_ any ...) #'(stlc:λ any ...)]))
(define-for-syntax (apply-forall ∀τ τs)
(define ctx (syntax-local-make-definition-context))
(define id (generate-temporary))
(syntax-local-bind-syntaxes
(list id)
(syntax-parse ∀τ #:datum-literals ()
[( (X ...) τbody)
#'(λ (stx)
(syntax-parse stx
[(_ (τ (... ...)))
#:with (X ...) #'(τ (... ...))
#'τbody]))])
ctx)
(local-expand #`(#,id #,τs) 'expression (list #'#%app) ctx))
; #%app -----------------------------------------------------------------------
(define-syntax (app/tc stx)
(syntax-parse stx #:literals ( void)
; #:datum-literals (:t)
; [(_ :t x) #'(printf "~a : ~a\n" 'x (hash-ref runtime-env 'x))]
[(_ e_fn τs e_arg ...)
#:when (curly-parens? #'τs)
; #:with (τ ...) #'τs
#:with (e_fn+ e_arg+ ...) (stx-map expand/df #'(e_fn e_arg ...))
; #:when (printf "e_fn: ~a\n" (typeof #'e_fn+))
; #:when (printf "τs: ~a\n" #'τs)
#:with ( (X ...) ( τX ...)) (typeof #'e_fn+)
; #:when (printf "applied: ~a\n" (apply-forall (typeof #'e_fn+) #'τs))
#:with ( τ_arg ... τ_res) (apply-forall (typeof #'e_fn+) #'τs)
#:when (stx-andmap assert-type #'(e_arg+ ...) #'(τ_arg ...))
( (syntax/loc stx (#%app e_fn+ e_arg+ ...)) #'τ_res)]
[(_ any ...) #:when (printf "shouldnt get here\n") #'(stlc:#%app any ...)]))
(define-syntax (letrec/tc stx)
(syntax-parse stx #:datum-literals (:)
[(_ ([f:id : τ_f e_f] ...) body ... body_result)
#:with (_ ([(f+) e_f+] ...) body+ ... body_result+)
(expand/df #'(letrec ([f e_f] ...) body ... body_result))
(syntax/loc stx (letrec ([f+ e_f+] ...) body+ ... body_result+))]))
;; module-begin and associated stx-classes
;; - copied from stlc.rkt: 2014-08-14
(begin-for-syntax
(define-syntax-class maybe-def #:datum-literals (: define variant) #:literals (define-type)
(pattern (define (f:id [x:id : τx] ...) body ...)
#:with τ_result (generate-temporary #'f)
#:when (fvs (set-add (fvs) (syntax->datum #'τ_result)))
#:attr name #'(f)
#:attr val #'((λ/tc ([x : τx] ...) body ...))
#:attr τ #'(( τx ... τ_result))
#:attr e #'() #:attr tydecl #'() #:attr names #'())
(pattern (define-type TypeName (variant (Cons fieldτ ...) ...))
#:attr name #'() #:attr τ #'() #:attr val #'() #:attr e #'()
#:attr tydecl #'((define-type TypeName (variant (Cons fieldτ ...) ...)))
#:attr names #'((Cons ...)))
(pattern exp:expr
#:attr name #'() #:attr τ #'() #:attr val #'() #:attr tydecl #'() #:attr names #'()
#:attr e #'(exp)))
(define-syntax-class strct #:literals (begin define-values define-syntaxes)
(pattern
(begin
(define-values (x ...) mk-strct-type-def)
(define-syntaxes (y) strct-info-def))
#:attr def-val #'(define-values (x ...) mk-strct-type-def)
#:attr def-syn #'(define-syntaxes (y) strct-info-def)))
(define-syntax-class def-val #:literals (define-values)
(pattern
(define-values (x ...) mk-strct-type-def)
#:attr lhs #'(x ...)
#:attr rhs #'mk-strct-type-def))
(define-syntax-class def-syn #:literals (define-syntaxes)
(pattern
(define-syntaxes (x) strct-info-def)
#:attr lhs #'x
#:attr rhs #'strct-info-def))
)
(define-syntax (module-begin/tc stx)
(syntax-parse stx #:literals (begin)
[(_ mb-form:maybe-def ...)
#:with (deftype ...) (template ((?@ . mb-form.tydecl) ...))
#:with ((begin deftype+ ...) ...) (stx-map expand/df/module-ctx #'(deftype ...))
#:with (structdef ...) (stx-flatten #'((deftype+ ...) ...))
#:with (structdef+:strct ...) (stx-map expand/df/module-ctx #'(structdef ...))
;; <----- everything before here gets called twice (eg try inserting printf)
;; (the expand on the previous line must be calling module begin?)
#:with (def-val:def-val ...) #'(structdef+.def-val ...)
#:with (def-val-lhs ...) #'(def-val.lhs ...)
#:with (def-val-rhs ...) #'(def-val.rhs ...)
#:with (def-syn:def-syn ...) #'(structdef+.def-syn ...)
#:with (def-syn-lhs ...) #'(def-syn.lhs ...)
#:with (def-syn-rhs ...) #'(def-syn.rhs ...)
#:with (f ...) (template ((?@ . mb-form.name) ...))
#:with (v ...) (template ((?@ . mb-form.val) ...))
#:with (τ ...) (template ((?@ . mb-form.τ) ...))
#:with (e ...) (template ((?@ . mb-form.e) ...))
#:when (Γ (type-env-extend #'([f τ] ...)))
; #:when (printf "fvs :~a\n" (fvs))
;; NOTE: for struct def, define-values *must* come before define-syntaxes
;; ow, error: "Just10: unbound identifier; also, no #%top syntax transformer is bound"
(quasisyntax/loc stx
(#%module-begin
#,(expand/df #'(let-values ([def-val-lhs def-val-rhs] ...)
(let-syntax ([def-syn-lhs def-syn-rhs] ...)
(letrec/tc ([f : τ v] ...) e ... (void)))))
#;(define #,(datum->syntax stx 'runtime-env)
(for/hash ([x:τ '#,(map (λ () (cons (car ) (syntax->datum (cdr ))))
(hash->list (do-subst (Γ))))])
(values (car x:τ) (cdr x:τ))))
))]))
;; testing fns ----------------------------------------------------------------
;; for now, these are duplicated from stlc.rkt: 2014-08-14
;; type checking testing: -----------------------------------------------------
(require rackunit)
(require (for-syntax rackunit "typecheck.rkt"))
(provide check-equal?)
(provide check-type-error check-type check-type-and-result check-not-type)
(define-syntax (check-type-error stx)
(syntax-parse stx
[(_ e)
#:when (check-exn exn:fail? (λ () (expand/df #'e)))
#'(void)]))
(define-syntax (check-type stx)
(syntax-parse stx #:datum-literals (:)
[(_ e : τ)
#:with e+ (expand/df #'e)
#:when (check-true (assert-type #'e+ #'τ)
(format "Expected type ~a but got type ~a" #'τ (typeof #'e)))
#'(void)]))
(define-syntax (check-not-type stx)
(syntax-parse stx #:datum-literals (:)
[(_ e : τ)
#:with e+ (expand/df #'e)
#:when (check-false (type=? (typeof #'e+) #'τ)
(format "Expected type to not be ~a but got type ~a" #'τ (typeof #'e)))
#'(void)]))
(define-syntax (check-type-and-result stx)
(syntax-parse stx #:datum-literals (: =>)
[(_ e : τ => v)
#'(begin (check-type e : τ)
(check-equal? e v))]))