226 lines
10 KiB
Racket
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 (λ (xτ) (cons (car xτ) (syntax->datum (cdr xτ))))
|
|
(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))]))
|