#lang turnstile (provide bind discard ann if when unless let let* cond else match tuple unit select error define-tuple match-define (for-syntax (all-defined-out))) (require "core-types.rkt") (require (only-in "prim.rkt" Bool String #%datum)) (require (postfix-in - racket/match)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Patterns ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define-typed-syntax (bind x:id τ:type) ≫ ---------------------------------------- [⊢ (error- 'bind "escaped") (⇒ : (Bind τ))]) (define-typed-syntax discard [_ ≫ -------------------- [⊢ (error- 'discard "escaped") (⇒ : Discard)]]) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Core-ish forms ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; copied from stlc (define-typed-syntax (ann e (~optional (~datum :)) τ:type) ≫ [⊢ e ≫ e- (⇐ : τ.norm)] #:fail-unless (pure? #'e-) "expression must be pure" ------------------------------------------------ [⊢ e- (⇒ : τ.norm) ]) ;; copied from ext-stlc (define-typed-syntax if [(_ e_tst e1 e2) ⇐ τ-expected ≫ [⊢ e_tst ≫ e_tst- ⇒ _] ; Any non-false value is truthy. #:fail-unless (pure? #'e_tst-) "expression must be pure" [⊢ e1 ≫ e1- (⇐ : τ-expected) (⇒ ν-ep (~effs eps1 ...)) (⇒ ν-f (~effs fs1 ...)) (⇒ ν-s (~effs ss1 ...))] [⊢ e2 ≫ e2- (⇐ : τ-expected) (⇒ ν-ep (~effs eps2 ...)) (⇒ ν-f (~effs fs2 ...)) (⇒ ν-s (~effs ss2 ...))] -------- [⊢ (if- e_tst- e1- e2-) (⇒ : τ-expected) (⇒ ν-ep (eps1 ... eps2 ...)) (⇒ ν-f #,(make-Branch #'((fs1 ...) (fs2 ...)))) (⇒ ν-s (ss1 ... ss2 ...))]] [(_ e_tst e1 e2) ≫ [⊢ e_tst ≫ e_tst- ⇒ _] ; Any non-false value is truthy. #:fail-unless (pure? #'e_tst-) "expression must be pure" [⊢ e1 ≫ e1- (⇒ : τ1) (⇒ ν-ep (~effs eps1 ...)) (⇒ ν-f (~effs fs1 ...)) (⇒ ν-s (~effs ss1 ...))] [⊢ e2 ≫ e2- (⇒ : τ2) (⇒ ν-ep (~effs eps2 ...)) (⇒ ν-f (~effs fs2 ...)) (⇒ ν-s (~effs ss2 ...))] #:with τ (mk-U- #'(τ1 τ2)) -------- [⊢ (if- e_tst- e1- e2-) (⇒ : τ) (⇒ ν-ep (eps1 ... eps2 ...)) (⇒ ν-f #,(make-Branch #'((fs1 ...) (fs2 ...)))) (⇒ ν-s (ss1 ... ss2 ...))]]) (define-typed-syntax (when e s ...+) ≫ ------------------------------------ [≻ (if e (let () s ...) #f)]) (define-typed-syntax (unless e s ...+) ≫ ------------------------------------ [≻ (if e #f (let () s ...))]) ;; copied from ext-stlc (define-typed-syntax let [(_ ([x e] ...) e_body ...) ⇐ τ_expected ≫ [⊢ e ≫ e- ⇒ : τ_x] ... #:fail-unless (stx-andmap pure? #'(e- ...)) "expressions must be pure" [[x ≫ x- : τ_x] ... ⊢ (begin e_body ...) ≫ e_body- (⇐ : τ_expected) (⇒ ν-ep (~effs eps ...)) (⇒ ν-f (~effs fs ...)) (⇒ ν-s (~effs ss ...))] ---------------------------------------------------------- [⊢ (let- ([x- e-] ...) e_body-) (⇒ : τ_expected) (⇒ ν-ep (eps ...)) (⇒ ν-f (fs ...)) (⇒ ν-s (ss ...))]] [(_ ([x e] ...) e_body ...) ≫ [⊢ e ≫ e- ⇒ : τ_x] ... #:fail-unless (stx-andmap pure? #'(e- ...)) "expressions must be pure" [[x ≫ x- : τ_x] ... ⊢ (begin e_body ...) ≫ e_body- (⇒ : τ_body) (⇒ ν-ep (~effs eps ...)) (⇒ ν-f (~effs fs ...)) (⇒ ν-s (~effs ss ...))] ---------------------------------------------------------- [⊢ (let- ([x- e-] ...) e_body-) (⇒ : τ_body) (⇒ ν-ep (eps ...)) (⇒ ν-f (fs ...)) (⇒ ν-s (ss ...))]]) ;; copied from ext-stlc (define-typed-syntax let* [(_ () e_body ...) ≫ -------- [≻ (begin e_body ...)]] [(_ ([x e] [x_rst e_rst] ...) e_body ...) ≫ -------- [≻ (let ([x e]) (let* ([x_rst e_rst] ...) e_body ...))]]) (define-typed-syntax (cond [pred:expr s ...+] ...+) ≫ [⊢ pred ≫ pred- (⇐ : Bool)] ... #:fail-unless (stx-andmap pure? #'(pred- ...)) "predicates must be pure" [⊢ (begin s ...) ≫ s- (⇒ : τ-s) (⇒ ν-ep (~effs eps ...)) (⇒ ν-f (~effs fs ...)) (⇒ ν-s (~effs ss ...))] ... ------------------------------------------------ [⊢ (cond- [pred- s-] ...) (⇒ : (U τ-s ...)) (⇒ ν-ep (eps ... ...)) (⇒ ν-f #,(make-Branch #'((fs ...) ...))) (⇒ ν-s (ss ... ...))]) (define else #t) (define-typed-syntax (match e [p s ...+] ...+) ≫ [⊢ e ≫ e- (⇒ : τ-e)] #:fail-unless (pure? #'e-) "expression must be pure" #:with (p/e ...) (for/list ([pat (in-syntax #'(p ...))]) (elaborate-pattern/with-type pat #'τ-e)) #:with (([x τ:type] ...) ...) (stx-map pat-bindings #'(p/e ...)) [[x ≫ x- : τ.norm] ... ⊢ (begin s ...) ≫ s- (⇒ : τ-s) (⇒ ν-ep (~effs eps ...)) (⇒ ν-f (~effs fs ...)) (⇒ ν-s (~effs ss ...))] ... ;; REALLY not sure how to handle p/p-/p.match-pattern, ;; particularly w.r.t. typed terms that appear in p.match-pattern [⊢ p/e ≫ p-- ⇒ τ-p] ... #:fail-unless (project-safe? #'τ-e (mk-U*- #'(τ-p ...))) "possibly unsafe pattern match" #:fail-unless (stx-andmap pure? #'(p-- ...)) "patterns must be pure" #:with (p- ...) (stx-map (lambda (p x-s xs) (substs x-s xs (compile-match-pattern p))) #'(p/e ...) #'((x- ...) ...) #'((x ...) ...)) -------------------------------------------------------------- [⊢ (match- e- [p- s-] ... [_ (#%app- error- "incomplete pattern match")]) (⇒ : (U τ-s ...)) (⇒ ν-ep #,(make-Branch #'((eps ...) ...))) (⇒ ν-f #,(make-Branch #'((fs ...) ...))) (⇒ ν-s #,(make-Branch #'((ss ...) ...)))]) (define-typed-syntax (tuple e:expr ...) ≫ [⊢ e ≫ e- (⇒ : τ)] ... #:fail-unless (stx-andmap pure? #'(e- ...)) "expressions not allowed to have effects" ----------------------- [⊢ (#%app- list- 'tuple e- ...) (⇒ : (Tuple τ ...))]) (define unit : Unit (tuple)) (define-typed-syntax (select n:nat e:expr) ≫ [⊢ e ≫ e- (⇒ : (~Tuple τ ...))] #:fail-unless (pure? #'e-) "expression not allowed to have effects" #:do [(define i (syntax->datum #'n))] #:fail-unless (< i (stx-length #'(τ ...))) "index out of range" #:with τr (list-ref (stx->list #'(τ ...)) i) -------------------------------------------------------------- [⊢ (#%app- tuple-select n e-) (⇒ : τr)]) (define- (tuple-select n t) (#%app- list-ref- t (#%app- add1- n))) (define-typed-syntax (error msg args ...) ≫ [⊢ msg ≫ msg- (⇐ : String)] [⊢ args ≫ args- (⇒ : τ)] ... #:fail-unless (all-pure? #'(msg- args- ...)) "expressions must be pure" ---------------------------------------- [⊢ (#%app- error- msg- args- ...) (⇒ : ⊥)]) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Helpers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; pat -> ([Id Type] ...) (define-for-syntax (pat-bindings stx) (syntax-parse stx #:datum-literals (bind tuple) [(bind x:id τ:type) #'([x τ])] [(tuple p ...) #:with (([x:id τ:type] ...) ...) (stx-map pat-bindings #'(p ...)) #'([x τ] ... ...)] #;[(k:kons1 p) (pat-bindings #'p)] [(~constructor-exp cons p ...) #:with (([x:id τ:type] ...) ...) (stx-map pat-bindings #'(p ...)) #'([x τ] ... ...)] [_ #'()])) (begin-for-syntax ;; Any -> Bool (define (dollar-variable? x) (and (identifier? x) (char=? (string-ref (symbol->string (syntax-e x)) 0) #\$))) ;; dollar-id -> Identifier (define (un-dollar x) (datum->syntax x (string->symbol (substring (symbol->string (syntax-e x)) 1)))) (define-syntax-class dollar-id #:attributes (id) (pattern x:id #:when (dollar-variable? #'x) #:attr id (un-dollar #'x))) ;; match things of the for "$X...:Y..." where X and Y are things without ;; spaces (i.e. likely but not definitely legal identifiers) (define DOLLAR-ANN-RX #px"^\\$(\\S*):(\\S*)$") ;; Any -> RegexpMatchResults (define (dollar-ann-variable? x) (and (identifier? x) (regexp-match DOLLAR-ANN-RX (symbol->string (syntax-e x))))) (define-syntax-class dollar-ann-id #:attributes (id ty) (pattern x:id #:do [(define match? (dollar-ann-variable? #'x))] #:when match? #:attr id (datum->syntax #'x (string->symbol (second match?))) #:attr ty (datum->syntax #'x (string->symbol (third match?))))) ;; expand uses of $ short-hand ;; doesn't handle uses of $id or ($) w/o a type (define (elaborate-pattern pat) (syntax-parse pat #:datum-literals (tuple _ $) [_ #'discard] [x:dollar-ann-id (syntax/loc pat (bind x.id x.ty))] [($ x:id ty) (syntax/loc pat (bind x ty))] [(tuple p ...) (quasisyntax/loc pat (tuple #,@(stx-map elaborate-pattern #'(p ...))))] [(k:kons1 p) (quasisyntax/loc pat (k #,(elaborate-pattern #'p)))] [(~constructor-exp ctor p ...) (quasisyntax/loc pat (ctor #,@(stx-map elaborate-pattern #'(p ...))))] [e:expr #'e])) (define (elaborate-pattern/with-type pat ty) (syntax-parse pat #:datum-literals (tuple $) [(~datum _) #'discard] [x:dollar-ann-id (syntax/loc pat (bind x.id x.ty))] [x:dollar-id (quasisyntax/loc pat (bind x.id #,ty))] [($ x:id ty) (syntax/loc pat (bind x ty))] [($ x:id) (quasisyntax/loc pat (bind x #,ty))] [(tuple p ...) (define (matching? t) (syntax-parse t [(~Tuple tt ...) #:when (stx-length=? #'(p ...) #'(tt ...)) #t] [_ #f])) (define (proj t i) (syntax-parse t [(~Tuple tt ...) (if (= i -1) t (stx-list-ref #'(tt ...) i))] [(~U* (~or (~and tt (~fail #:unless (or (U*? #'tt) (matching? #'tt)))) _) ...) (mk-U- (stx-map (lambda (x) (proj x i)) #'(tt ...)))] [_ (mk-U*- '())])) (define selected (proj ty -1)) (define sub-pats (for/list ([pat (in-syntax #'(p ...))] [i (in-naturals)]) (elaborate-pattern/with-type pat (proj selected i)))) (quasisyntax/loc pat (tuple #,@sub-pats))] [(~constructor-exp ctor p ...) (define tag (ctor-type-tag #'ctor)) (define (matching? t) (syntax-parse t [(~constructor-type tag2 tt ...) #:when (equal? tag (syntax-e #'tag2)) #:when (stx-length=? #'(p ...) #'(tt ...)) #t] [_ #f])) (define (proj t i) (syntax-parse t [(~constructor-type _ tt ...) (if (= i -1) t (stx-list-ref #'(tt ...) i))] [(~U* (~or (~and tt (~fail #:unless (or (U*? #'tt) (matching? #'tt)))) _) ...) (mk-U- (stx-map (lambda (x) (proj x i)) #'(tt ...)))] [_ (mk-U*- '())])) (define selected (proj ty -1)) (define sub-pats (for/list ([pat (in-syntax #'(p ...))] [i (in-naturals)]) (elaborate-pattern/with-type pat (proj selected i)))) (quasisyntax/loc pat (ctor #,@sub-pats))] [e:expr #'e]))) ;; TODO - figure out why this needs different list identifiers (define-for-syntax (compile-pattern pat list-binding bind-id-transformer exp-transformer) (define (l-e stx) (local-expand stx 'expression '())) (let loop ([pat pat]) (syntax-parse pat #:datum-literals (tuple discard bind) [(tuple p ...) #`(#,list-binding 'tuple #,@(stx-map loop #'(p ...)))] #;[(k:kons1 p) #`(#,(kons1->constructor #'k) #,(loop #'p))] [(bind x:id τ:type) (bind-id-transformer #'x)] [discard #'_] [(~constructor-exp ctor p ...) (define/with-syntax uctor (untyped-ctor #'ctor)) #`(uctor #,@(stx-map loop #'(p ...)))] [_ ;; local expanding "expression-y" syntax allows variable references to transform ;; according to the mappings set up by turnstile. (exp-transformer (l-e pat))]))) (define-for-syntax (compile-match-pattern pat) (compile-pattern pat #'list identity (lambda (exp) #`(==- #,exp)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Derived Forms ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define-typed-syntax (define-tuple (x:id ...) e:expr) ≫ [⊢ e ≫ e- (⇒ (~Tuple τ ...))] #:fail-unless (stx-length=? #'(x ...) #'(τ ...)) "mismatched size" #:fail-unless (pure? #'e-) "expr must be pure" #:with (sel ...) (for/list ([y (in-syntax #'(x ...))] [t (in-syntax #'(τ ...))] [i (in-naturals)]) (quasisyntax/loc this-syntax (select #,i it))) ---------------------------------------- [≻ (begin (define it e-) (define x : τ sel) ...)]) (define-typed-syntax (match-define pat:expr e:expr) ≫ [⊢ e ≫ e- (⇒ : τ-e)] #:with pat+ (elaborate-pattern/with-type #'pat #'τ-e) #:with ([x τ] ...) (pat-bindings #'pat+) ---------------------------------------- [≻ (define-tuple (x ...) (match e- [pat+ (tuple x ...)]))]) ;; extremely limited match-define for `define-constructor`-d things #;(define-typed-syntax (match-define (~constructor-exp ctor x:id ...) e:expr) ≫ [⊢ e ≫ e- (⇒ (~constructor-type tag1 τ ...))] #:fail-unless (stx-length=? #'(x ...) #'(τ ...)) "mismatched size" [⊢ (ctor (bind x τ) ...) ≫ pat- (⇒ (~constructor-type tag2 _ ...))] #:fail-unless (equal? #'tag1 #'tag2) (~format "type mismatch: ~a, ~a" #'tag1 #'tag2) ------------------------------------------------------------ )