343 lines
13 KiB
Racket
343 lines
13 KiB
Racket
#lang racket/base
|
|
(require
|
|
(for-syntax racket/base syntax/parse syntax/parse/experimental/template
|
|
racket/set syntax/id-table syntax/stx racket/list racket/syntax
|
|
"stx-utils.rkt")
|
|
(for-meta 2 racket/base syntax/parse))
|
|
(provide
|
|
(except-out
|
|
(all-from-out racket/base)
|
|
λ #%app + #%datum let letrec cons null null? begin void
|
|
#%module-begin if
|
|
;#%top define
|
|
))
|
|
|
|
|
|
(provide
|
|
(rename-out
|
|
[λ/tc λ] [app/tc #%app] [let/tc let] [letrec/tc letrec]
|
|
[begin/tc begin] [void/tc void]
|
|
[if/tc if] [+/tc +]
|
|
;[top/tc #%top] [define/tc define]
|
|
[datum/tc #%datum] [module-begin/tc #%module-begin]
|
|
[cons/tc cons] [null/tc null] [null?/tc null?] [first/tc first] [rest/tc rest]))
|
|
|
|
;; system f; extended from stlc.rkt 2014-08-08
|
|
|
|
;; TODO:
|
|
;; - type of top level (or module level) vars not checked
|
|
|
|
;; for types, just need the identifier bound
|
|
(define-syntax-rule (define-type τ) (begin (define τ #f) (provide τ)))
|
|
(define-syntax-rule (define-types τ ...) (begin (define-type τ) ...))
|
|
(define-types Int String Bool → Listof Unit)
|
|
|
|
;; type environment
|
|
(begin-for-syntax
|
|
(define base-type-env (hash))
|
|
;; Γ : [Hashof var-symbol => type-stx]
|
|
;; - can't use free-identifier=? for the hash table (or free-id-table)
|
|
;; because env must be set before expanding λ body (ie before going under λ)
|
|
;; so x's in the body won't be free-id=? to the one in the table
|
|
;; use symbols instead of identifiers for now --- should be fine because
|
|
;; I'm manually managing the environment
|
|
(define Γ (make-parameter base-type-env))
|
|
|
|
;; generated type vars
|
|
(define fvs (make-parameter (set)))
|
|
(define fvs-subst (make-parameter (hash)))
|
|
(define (fvs-subst-set x τ)
|
|
(hash-set (fvs-subst) (syntax->datum x) τ))
|
|
(define (do-subst/τ τ)
|
|
(syntax-parse τ
|
|
[x:id
|
|
#:when (set-member? (fvs) (syntax->datum #'x))
|
|
(hash-ref (fvs-subst) (syntax->datum #'x) #'???)]
|
|
[τ:id #'τ]
|
|
[(tycon τ ...)
|
|
#:with (τ-subst ...) (stx-map do-subst/τ #'(τ ...))
|
|
#'(tycon τ-subst ...)]))
|
|
(define (do-subst h)
|
|
(for/hash ([(x τ) (in-hash h)])
|
|
(values x (do-subst/τ τ)))))
|
|
|
|
;; testing fns ----------------------------------------------------------------
|
|
(require (for-syntax rackunit))
|
|
(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? (λ () (local-expand #'e 'expression null)))
|
|
#'(void)]))
|
|
(define-syntax (check-type stx)
|
|
(syntax-parse stx #:datum-literals (:)
|
|
[(_ e : τ)
|
|
#:with e+ (local-expand #'e 'expression null)
|
|
#: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+ (local-expand #'e 'expression null)
|
|
#: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))]))
|
|
(require rackunit)
|
|
(provide (rename-out [my-check-equal? check-equal?]))
|
|
(define-syntax-rule (my-check-equal? x y) (check-equal? x y))
|
|
|
|
(define-for-syntax (type=? τ1 τ2)
|
|
; (printf "type= ~a ~a\n" (syntax->datum τ1) (syntax->datum τ2))
|
|
(syntax-parse #`(#,τ1 #,τ2) #:literals (→)
|
|
[(x:id τ)
|
|
#:when (and (set-member? (fvs) (syntax->datum #'x))
|
|
(hash-has-key? (fvs-subst) (syntax->datum #'x)))
|
|
(type=? (hash-ref (fvs-subst) (syntax->datum #'x)) #'τ)]
|
|
[(x:id τ)
|
|
#:when (set-member? (fvs) (syntax->datum #'x))
|
|
#:when (fvs-subst (fvs-subst-set #'x #'τ))
|
|
#t]
|
|
[(τ x:id)
|
|
#:when (and (set-member? (fvs) (syntax->datum #'x))
|
|
(hash-has-key? (fvs-subst) (syntax->datum #'x)))
|
|
(type=? (hash-ref (fvs-subst) (syntax->datum #'x)) #'τ)]
|
|
[(τ x:id)
|
|
#:when (set-member? (fvs) (syntax->datum #'x))
|
|
#:when (fvs-subst (fvs-subst-set #'x #'τ))
|
|
#t]
|
|
[(x:id y:id) (free-identifier=? τ1 τ2)]
|
|
[((tycon1 τ1 ...) (tycon2 τ2 ...))
|
|
(and (free-identifier=? #'tycon1 #'tycon2)
|
|
(stx-andmap type=? #'(τ1 ...) #'(τ2 ...)))]
|
|
[_ #f]))
|
|
|
|
;; return #t if (typeof e)=τ, else type error
|
|
(begin-for-syntax
|
|
(define (assert-type e τ)
|
|
(or (type=? (typeof e) τ)
|
|
(error 'TYPE-ERROR "~a (~a:~a) has type ~a, but should have type ~a"
|
|
(syntax->datum e)
|
|
(syntax-line e) (syntax-column e)
|
|
(syntax->datum (typeof e))
|
|
(syntax->datum τ))))
|
|
(define (assert-Unit-type e) (assert-type e #'Unit))
|
|
(define (assert-Int-type e) (assert-type e #'Int)))
|
|
|
|
;; type env and other helper fns ----------------------------------------------
|
|
;; attaches type τ to e (as syntax property)
|
|
(define-for-syntax (⊢ e τ) (syntax-property e 'type τ))
|
|
|
|
;; retrieves type of τ (from syntax property)
|
|
(define-for-syntax (typeof stx) (syntax-property stx 'type))
|
|
|
|
(define-for-syntax (type-env-lookup x) (hash-ref (Γ) (syntax->datum x)))
|
|
;; returns a new hash table extended with type associations x:τs
|
|
(define-for-syntax (type-env-extend x:τs)
|
|
(define xs (stx-map stx-car x:τs))
|
|
(define τs (stx-map stx-cadr x:τs))
|
|
(apply hash-set* (Γ) (append-map (λ (x τ) (list (syntax->datum x) τ)) xs τs)))
|
|
;; must be macro because type env must be extended first, before expandinb body
|
|
(begin-for-syntax
|
|
(define-syntax (with-extended-type-env stx)
|
|
(syntax-parse stx
|
|
[(_ x-τs e)
|
|
#'(parameterize ([Γ (type-env-extend x-τs)]) e)])))
|
|
|
|
;; depth-first expand
|
|
(define-for-syntax (expand/df e)
|
|
(if (identifier? e)
|
|
(⊢ e (type-env-lookup e)) ; handle this here bc there's no #%var form
|
|
(local-expand e 'expression null)))
|
|
(define-for-syntax (expand/df/module-ctx def)
|
|
(local-expand def 'module null))
|
|
(define-for-syntax (expand/df/mb-ctx def)
|
|
(local-expand def 'module-begin null))
|
|
|
|
|
|
;; typed forms ----------------------------------------------------------------
|
|
(define-syntax (datum/tc stx)
|
|
(syntax-parse stx
|
|
[(_ . n:integer) (⊢ (syntax/loc stx (#%datum . n)) #'Int)]
|
|
[(_ . s:str) (⊢ (syntax/loc stx (#%datum . s)) #'String)]
|
|
[(_ . b:boolean) (⊢ (syntax/loc stx (#%datum . b)) #'Bool)]
|
|
[(_ . x)
|
|
#:when (error 'TYPE-ERROR "~a (~a:~a) has unknown type"
|
|
#'x (syntax-line #'x) (syntax-column #'x))
|
|
(syntax/loc stx (#%datum . x))]))
|
|
|
|
(define-syntax (begin/tc stx)
|
|
(syntax-parse stx
|
|
[(_ e ... e_result)
|
|
#:with (e+ ... e_result+) (stx-map expand/df #'(e ... e_result))
|
|
#:when (stx-andmap assert-Unit-type #'(e+ ...))
|
|
(⊢ (syntax/loc stx (begin e+ ... e_result+)) (typeof #'e_result+))]))
|
|
|
|
(define-syntax (void/tc stx)
|
|
(syntax-parse stx
|
|
[(_) (⊢ (syntax/loc stx (void)) #'Unit)]))
|
|
|
|
(define-syntax (+/tc stx)
|
|
(syntax-parse stx
|
|
[(_ e ...)
|
|
#:with (e+ ...) (stx-map expand/df #'(e ...))
|
|
#:when (stx-andmap assert-Int-type #'(e+ ...))
|
|
(⊢ (syntax/loc stx (+ e+ ...)) #'Int)]))
|
|
|
|
(define-syntax (λ/tc stx)
|
|
(syntax-parse stx #:datum-literals (:)
|
|
[(_ ([x:id : τ] ...) e ... e_result)
|
|
;; 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 the implicit begin
|
|
#:when (stx-map assert-Unit-type #'(e+ ...); [(_ x:id e)
|
|
; #:with e))
|
|
)
|
|
#:with τ_body (typeof #'e_result+)
|
|
(⊢ (syntax/loc stx (lam xs e+ ... e_result+)) #'(→ τ ... τ_body))]))
|
|
|
|
(define-syntax (let/tc stx)
|
|
(syntax-parse stx #:datum-literals (:)
|
|
[(_ ([x:id e_x] ...) e ... e_result)
|
|
#:with (e_x+ ...) (stx-map expand/df #'(e_x ...))
|
|
#:with (τ ...) (stx-map typeof #'(e_x+ ...))
|
|
#:with (lam (x+ ...) e+ ... e_result+)
|
|
(with-extended-type-env #'([x τ] ...)
|
|
(expand/df #'(λ (x ...) e ... e_result)))
|
|
#:when (stx-andmap assert-Unit-type #'(e+ ...))
|
|
(⊢ (syntax/loc stx (let ([x+ e_x+] ...) e+ ... e_result+)) (typeof #'e_result+))]))
|
|
|
|
(define-syntax (letrec/tc stx)
|
|
(syntax-parse stx #:datum-literals (:)
|
|
[(_ ([f:id : τ_f e_f] ...) body ... body_result)
|
|
; #:when (printf "letrec: ~a\n" (expand/df #'(letrec ([f e_f] ...) body ... body_result)))
|
|
#:with (lrec ([(f+) e_f+] ...) body+ ... body_result+)
|
|
(expand/df #'(letrec ([f e_f] ...) body ... body_result))
|
|
; #:with (lam (f+ ...) e_f+ ...)
|
|
; (expand/df #'(λ (f ...) e_f ...))
|
|
; #:with (lam2 fs body+ ... body_result+)
|
|
; (expand/df #'(λ (f ...) body ... body_result)) ; type env already extended by mod-beg
|
|
; #:with τ_result (typeof #'body_result+)
|
|
(syntax/loc stx (letrec ([f+ e_f+] ...) body+ ... body_result+))]))
|
|
|
|
; #%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 e_arg ...)
|
|
#:with (e_fn+ e_arg+ ...) (stx-map expand/df #'(e_fn e_arg ...))
|
|
#:with (→ τ ... τ_res) (typeof #'e_fn+)
|
|
#:when (stx-andmap assert-type #'(e_arg+ ...) #'(τ ...))
|
|
(⊢ (syntax/loc stx (#%app e_fn+ e_arg+ ...)) #'τ_res)]))
|
|
|
|
(define-syntax (if/tc stx)
|
|
(syntax-parse stx
|
|
[(_ e_test e1 e2)
|
|
#:with e_test+ (expand/df #'e_test)
|
|
#:when (assert-type #'e_test+ #'Bool)
|
|
#:with e1+ (expand/df #'e1)
|
|
#:with e2+ (expand/df #'e2)
|
|
#:when (type=? (typeof #'e1+) (typeof #'e2+))
|
|
(⊢ (syntax/loc stx (if e_test+ e1+ e2+)) (typeof #'e1+))]))
|
|
|
|
;; lists ----------------------------------------------------------------------
|
|
(define-syntax (cons/tc stx)
|
|
(syntax-parse stx
|
|
[(_ {T} e1 e2)
|
|
#:with e1+ (expand/df #'e1)
|
|
#:with e2+ (expand/df #'e2)
|
|
#:when (assert-type #'e1+ #'T)
|
|
#:when (assert-type #'e2+ #'(Listof T))
|
|
(⊢ (syntax/loc stx (cons e1+ e2+)) #'(Listof T))]))
|
|
(define-syntax (null/tc stx)
|
|
(syntax-parse stx
|
|
[(_ {T}) (⊢ (syntax/loc stx null) #'(Listof T))]))
|
|
(define-syntax (null?/tc stx)
|
|
(syntax-parse stx
|
|
[(_ {T} e)
|
|
#:with e+ (expand/df #'e)
|
|
#:when (assert-type #'e+ #'(Listof T))
|
|
(⊢ (syntax/loc stx (null? e+)) #'Bool)]))
|
|
(define-syntax (first/tc stx)
|
|
(syntax-parse stx
|
|
[(_ {T} e)
|
|
#:with e+ (expand/df #'e)
|
|
#:when (assert-type #'e+ #'(Listof T))
|
|
(⊢ (syntax/loc stx (car e+)) #'T)]))
|
|
(define-syntax (rest/tc stx)
|
|
(syntax-parse stx
|
|
[(_ {T} e)
|
|
#:with e+ (expand/df #'e)
|
|
#:when (assert-type #'e+ #'(Listof T))
|
|
(⊢ (syntax/loc stx (cdr e+)) #'(Listof T))]))
|
|
|
|
;; define, module-begin -------------------------------------------------------
|
|
#;(define-syntax (define/tc stx)
|
|
(syntax-parse stx #:datum-literals (:)
|
|
[(_ (f:id [x:id : τ] ...) e ... e_result)
|
|
#:with (g ...) (map (λ (fn) (datum->syntax (car (syntax->list #'(x ...))) fn)) (hash-keys (Γ)))
|
|
#:with (lam1 (g+ ...) (#%exp (lam2 (x+ ...) e+ ... e_result+)))
|
|
(with-extended-type-env #'([x τ] ...)
|
|
(expand/df #'(λ (g ...) (λ (x ...) e ... e_result))))
|
|
#:when (stx-andmap assert-Unit-type #'(e+ ...))
|
|
#:with τ_result (typeof #'e_result+)
|
|
#:when (Γ (type-env-extend #'([f (→ τ ... τ_result)])))
|
|
(⊢ (syntax/loc stx (define (f x+ ...) e+ ... e_result+)) #'Unit)]
|
|
; [(_ x:id e)
|
|
; #:with e
|
|
))
|
|
|
|
#;(define-syntax (top/tc stx)
|
|
(syntax-parse stx
|
|
[(_ . x:id)
|
|
#:when (printf "top: ~a\n" #'x)
|
|
#:with x+ (expand/df #'x)
|
|
#:when (printf "expanded top: ~a\n" #'x+)
|
|
(syntax/loc stx (#%top . x+))]))
|
|
|
|
(begin-for-syntax
|
|
(define-syntax-class maybe-def #:datum-literals (: define)
|
|
(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 #'())
|
|
; (pattern (define x:id exp:expr)
|
|
; #:attr name #'(x)
|
|
; #:attr val #'(exp)
|
|
; #:attr τ ???
|
|
(pattern exp:expr
|
|
#:attr name #'() #:attr τ #'() #:attr val #'()
|
|
#:attr e #'(exp))))
|
|
|
|
(define-syntax (module-begin/tc stx)
|
|
(syntax-parse stx
|
|
[(_ mb-form:maybe-def ...)
|
|
#: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))
|
|
; #:when (printf "mb: ~a\n" (syntax->datum (expand/df #'(letrec ([f v] ...) e ...))))
|
|
(quasisyntax/loc stx
|
|
(#%module-begin
|
|
#,(expand/df #'(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:τ))))
|
|
))]))
|