add ~typecheck and ~⊢ pattern expanders (#6)
* add ~typecheck and ~⊢ pattern expanders So that in normal macros, syntax classes, and normal syntax-parse expressions, you can use use the Turnstile syntax to do typechecking * add documentation for ~typecheck and ~⊢
This commit is contained in:
parent
f9199f6e37
commit
bbcdfaf9cf
171
turnstile/examples/tests/pat-expander-tests-def.rkt
Normal file
171
turnstile/examples/tests/pat-expander-tests-def.rkt
Normal file
|
@ -0,0 +1,171 @@
|
|||
#lang turnstile
|
||||
|
||||
(provide (all-defined-out))
|
||||
|
||||
(define-base-type Nothing)
|
||||
(define-base-type Bool)
|
||||
(define-base-type Int)
|
||||
(define-base-type String)
|
||||
(define-type-constructor Tuple #:arity >= 0)
|
||||
(define-type-constructor Listof #:arity = 1)
|
||||
(define-type-constructor Sequenceof #:arity >= 0)
|
||||
|
||||
(begin-for-syntax
|
||||
(define-splicing-syntax-class (for-clause-group env)
|
||||
#:attributes [[clause- 1] [env.x 1] [env.τ 1]]
|
||||
[pattern (~seq (~var clause (for-clause env))
|
||||
...)
|
||||
#:with [clause- ...] #'[clause.clause- ... ...]
|
||||
#:with [[env.x env.τ] ...] #'[[clause.env.x clause.env.τ] ... ...]])
|
||||
|
||||
(define-splicing-syntax-class (guard-clause env)
|
||||
#:attributes [[clause- 1]]
|
||||
[pattern (~and (~seq #:when bool:expr)
|
||||
(~typecheck
|
||||
#:with [[x τ_x] ...] env
|
||||
[[x ≫ x- : τ_x] ... ⊢ bool ≫ bool- ⇐ Bool]))
|
||||
#:with [clause- ...] #`[#:when (let- ([x- x] ...) bool-)]]
|
||||
[pattern (~and (~seq #:unless bool:expr)
|
||||
(~typecheck
|
||||
#:with [[x τ_x] ...] env
|
||||
[[x ≫ x- : τ_x] ... ⊢ bool ≫ bool- ⇐ Bool]))
|
||||
#:with [clause- ...] #`[#:unless (let- ([x- x] ...) bool-)]]
|
||||
[pattern (~and (~seq #:break bool:expr)
|
||||
(~typecheck
|
||||
#:with [[x τ_x] ...] env
|
||||
[[x ≫ x- : τ_x] ... ⊢ bool ≫ bool- ⇐ Bool]))
|
||||
#:with [clause- ...] #`[#:break (let- ([x- x] ...) bool-)]]
|
||||
[pattern (~and (~seq #:final bool:expr)
|
||||
(~typecheck
|
||||
#:with [[x τ_x] ...] env
|
||||
[[x ≫ x- : τ_x] ... ⊢ bool ≫ bool- ⇐ Bool]))
|
||||
#:with [clause- ...] #`[#:final (let- ([x- x] ...) bool-)]])
|
||||
|
||||
(define-splicing-syntax-class (for-clause env)
|
||||
#:attributes [[clause- 1] [env.x 1] [env.τ 1]]
|
||||
[pattern (~and [x:id seq:expr]
|
||||
(~typecheck
|
||||
#:with [[y τ_y] ...] env
|
||||
[[y ≫ y- : τ_y] ... ⊢ seq ≫ seq- ⇒ (~Sequenceof τ_x)]))
|
||||
#:with [clause- ...] #'[[x (let- ([y- y] ...) seq-)]]
|
||||
#:with [[env.x env.τ] ...] #'[[x τ_x]]]
|
||||
[pattern (~and [(x:id ...) seq:expr]
|
||||
(~typecheck
|
||||
#:with [[y τ_y] ...] env
|
||||
[[y ≫ y- : τ_y] ... ⊢ seq ≫ seq- ⇒ (~Sequenceof τ_x ...)]))
|
||||
#:fail-unless (stx-length=? #'[x ...] #'[τ_x ...])
|
||||
(format "expected a ~v-valued sequence, given a ~v-valued one"
|
||||
(stx-length #'[x ...])
|
||||
(stx-length #'[τ_x ...]))
|
||||
#:with [clause- ...] #'[[(x ...) (let- ([y- y] ...) seq-)]]
|
||||
#:with [[env.x env.τ] ...] #'[[x τ_x] ...]])
|
||||
|
||||
(define-syntax-class (for-clauses env)
|
||||
#:attributes [[clause- 1] [env.x 1] [env.τ 1]]
|
||||
[pattern ((~var group (for-clause-group env)))
|
||||
#:with [clause- ...] #'[group.clause- ...]
|
||||
#:with [[env.x env.τ] ...] #'[[group.env.x group.env.τ] ...]]
|
||||
[pattern ((~var fst (for-clause-group env))
|
||||
(~var guard (guard-clause (stx-append env #'[[fst.env.x fst.env.τ] ...])))
|
||||
.
|
||||
(~var rst (for-clauses (stx-append env #'[[fst.env.x fst.env.τ] ...]))))
|
||||
#:with [clause- ...] #'[fst.clause- ... guard.clause- ... rst.clause- ...]
|
||||
#:with [[env.x env.τ] ...] #'[[fst.env.x fst.env.τ] ... [rst.env.x rst.env.τ] ...]])
|
||||
)
|
||||
|
||||
;; ------------------------------------------------------------------------
|
||||
|
||||
;; for/list
|
||||
|
||||
(define-typed-syntax for/list
|
||||
[(_ (~var clauses (for-clauses #'[]))
|
||||
body) ≫
|
||||
[[clauses.env.x ≫ x- : clauses.env.τ] ...
|
||||
⊢ body ≫ body- ⇒ τ]
|
||||
--------
|
||||
[⊢ (for/list- (clauses.clause- ...)
|
||||
(let- ([x- clauses.env.x] ...) body-))
|
||||
⇒ (Listof τ)]])
|
||||
|
||||
(define-typed-syntax in-range
|
||||
[(_ n:expr) ≫
|
||||
[⊢ n ≫ n- ⇐ Int]
|
||||
--------
|
||||
[⊢ (in-range- n-) ⇒ (Sequenceof Int)]])
|
||||
|
||||
(define-typed-syntax in-naturals
|
||||
[(_) ≫ --- [⊢ (in-naturals-) ⇒ (Sequenceof Int)]]
|
||||
[(_ n:expr) ≫
|
||||
[⊢ n ≫ n- ⇐ Int]
|
||||
--------
|
||||
[⊢ (in-naturals- n-) ⇒ (Sequenceof Int)]])
|
||||
|
||||
(define-typed-syntax in-list
|
||||
[(_ lst:expr) ≫
|
||||
[⊢ lst ≫ lst- ⇒ (~Listof τ)]
|
||||
--------
|
||||
[⊢ (in-list- lst-) ⇒ (Sequenceof τ)]])
|
||||
|
||||
(define-typed-syntax in-indexed
|
||||
[(_ seq:expr) ≫
|
||||
[⊢ seq ≫ seq- ⇒ (~Sequenceof τ)]
|
||||
--------
|
||||
[⊢ (in-indexed- seq-) ⇒ (Sequenceof τ Int)]])
|
||||
|
||||
;; ------------------------------------------------------------------------
|
||||
|
||||
;; Constructing Literals, Tuples, and Lists
|
||||
|
||||
(define-typed-syntax #%datum
|
||||
[(_ . b:boolean) ≫ --- [⊢ (quote- b) ⇒ Bool]]
|
||||
[(_ . i:integer) ≫ --- [⊢ (quote- i) ⇒ Int]]
|
||||
[(_ . s:str) ≫ --- [⊢ (quote- s) ⇒ String]])
|
||||
|
||||
(define-typed-syntax tuple
|
||||
[(_ e:expr ...) ≫
|
||||
[⊢ [e ≫ e- ⇒ τ] ...]
|
||||
--------
|
||||
[⊢ (vector-immutable- e- ...) ⇒ (Tuple τ ...)]])
|
||||
|
||||
(define-typed-syntax list
|
||||
[(_) ≫ --- [⊢ (quote- ()) ⇒ (Listof Nothing)]]
|
||||
[(_ e0:expr e:expr ...) ≫
|
||||
[⊢ e0 ≫ e0- ⇒ τ]
|
||||
[⊢ [e ≫ e- ⇐ τ] ...]
|
||||
--------
|
||||
[⊢ (list- e0- e- ...) ⇒ (Listof τ)]])
|
||||
|
||||
;; ------------------------------------------------------------------------
|
||||
|
||||
;; Basic Bool Forms
|
||||
|
||||
(define-typed-syntax not
|
||||
[(_ b:expr) ≫ [⊢ b ≫ b- ⇐ Bool] --- [⊢ (not- b-) ⇒ Bool]])
|
||||
|
||||
(define-typed-syntax and
|
||||
[(_ b:expr ...) ≫
|
||||
[⊢ [b ≫ b- ⇐ Bool] ...]
|
||||
--------
|
||||
[⊢ (and- b- ...) ⇒ Bool]])
|
||||
|
||||
;; ------------------------------------------------------------------------
|
||||
|
||||
;; Basic Int Forms
|
||||
|
||||
(define-typed-syntax even?
|
||||
[(_ i:expr) ≫ [⊢ i ≫ i- ⇐ Int] --- [⊢ (even?- i-) ⇒ Bool]])
|
||||
|
||||
(define-typed-syntax odd?
|
||||
[(_ i:expr) ≫ [⊢ i ≫ i- ⇐ Int] --- [⊢ (odd?- i-) ⇒ Bool]])
|
||||
|
||||
;; ------------------------------------------------------------------------
|
||||
|
||||
;; Basic String Forms
|
||||
|
||||
(define-typed-syntax string=?
|
||||
[(_ a:expr b:expr) ≫
|
||||
[⊢ a ≫ a- ⇐ String]
|
||||
[⊢ b ≫ b- ⇐ String]
|
||||
--------
|
||||
[⊢ (string=?- a- b-) ⇒ Bool]])
|
||||
|
143
turnstile/examples/tests/pat-expander-tests.rkt
Normal file
143
turnstile/examples/tests/pat-expander-tests.rkt
Normal file
|
@ -0,0 +1,143 @@
|
|||
#lang turnstile
|
||||
|
||||
(require turnstile/rackunit-typechecking
|
||||
"pat-expander-tests-def.rkt")
|
||||
|
||||
;; The for/list macro defined in "pat-expander-tests-def.rkt" uses the
|
||||
;; ~typecheck pattern-expander to typecheck the for clauses within a
|
||||
;; syntax class.
|
||||
|
||||
;; These tests make sure that #:when conditions can refer to
|
||||
;; identifiers defined in previous clauses.
|
||||
|
||||
(check-type (for/list () 1) : (Listof Int) -> (list 1))
|
||||
(check-type (for/list () #t) : (Listof Bool) -> (list #t))
|
||||
(check-type (for/list () #f) : (Listof Bool) -> (list #f))
|
||||
|
||||
(check-type (for/list (#:when #t) 1) : (Listof Int) -> (list 1))
|
||||
(check-type (for/list (#:when #f) 1) : (Listof Int) -> (list))
|
||||
(check-type (for/list ([x (in-range 5)]) x)
|
||||
: (Listof Int)
|
||||
-> (list 0 1 2 3 4))
|
||||
|
||||
(check-type (for/list ([(s i) (in-indexed (in-list (list "a" "b" "c")))])
|
||||
(tuple s i))
|
||||
: (Listof (Tuple String Int))
|
||||
-> (list (tuple "a" 0) (tuple "b" 1) (tuple "c" 2)))
|
||||
|
||||
(check-type (for/list ([(s i) (in-indexed (in-list (list "a" "b" "c")))]
|
||||
#:when (even? i))
|
||||
(tuple s i))
|
||||
: (Listof (Tuple String Int))
|
||||
-> (list (tuple "a" 0) (tuple "c" 2)))
|
||||
|
||||
(check-type (for/list ([(s i) (in-indexed (in-list (list "a" "b" "c" "d" "e")))]
|
||||
#:when (even? i)
|
||||
[j (in-range i)])
|
||||
(tuple s i j))
|
||||
: (Listof (Tuple String Int Int))
|
||||
-> (list (tuple "c" 2 0)
|
||||
(tuple "c" 2 1)
|
||||
(tuple "e" 4 0)
|
||||
(tuple "e" 4 1)
|
||||
(tuple "e" 4 2)
|
||||
(tuple "e" 4 3)))
|
||||
|
||||
;; ------------------------------------------------------------------------
|
||||
|
||||
;; Test based on section 11 of the racket guide
|
||||
|
||||
(check-type (for/list ([book (in-list (list "Guide" "Reference" "Notes"))]
|
||||
#:when (not (string=? book "Notes"))
|
||||
[i (in-naturals 1)]
|
||||
[chapter (in-list (list "Intro" "Details" "Conclusion" "Index"))]
|
||||
#:when (not (string=? chapter "Index")))
|
||||
(tuple book i chapter))
|
||||
: (Listof (Tuple String Int String))
|
||||
-> (list (tuple "Guide" 1 "Intro")
|
||||
(tuple "Guide" 2 "Details")
|
||||
(tuple "Guide" 3 "Conclusion")
|
||||
(tuple "Reference" 1 "Intro")
|
||||
(tuple "Reference" 2 "Details")
|
||||
(tuple "Reference" 3 "Conclusion")))
|
||||
|
||||
(check-type (for/list ([book (in-list (list "Guide" "Story" "Reference"))]
|
||||
#:break (string=? book "Story")
|
||||
[chapter (in-list (list "Intro" "Details" "Conclusion"))])
|
||||
(tuple book chapter))
|
||||
: (Listof (Tuple String String))
|
||||
-> (list (tuple "Guide" "Intro")
|
||||
(tuple "Guide" "Details")
|
||||
(tuple "Guide" "Conclusion")))
|
||||
|
||||
(check-type (for/list ([book (in-list (list "Guide" "Story" "Reference"))]
|
||||
#:when #true
|
||||
[chapter (in-list (list "Intro" "Details" "Conclusion"))]
|
||||
#:break (and (string=? book "Story")
|
||||
(string=? chapter "Conclusion")))
|
||||
(tuple book chapter))
|
||||
: (Listof (Tuple String String))
|
||||
-> (list (tuple "Guide" "Intro")
|
||||
(tuple "Guide" "Details")
|
||||
(tuple "Guide" "Conclusion")
|
||||
(tuple "Story" "Intro")
|
||||
(tuple "Story" "Details")))
|
||||
|
||||
(check-type (for/list ([book (in-list (list "Guide" "Story" "Reference"))]
|
||||
#:when #true
|
||||
[chapter (in-list (list "Intro" "Details" "Conclusion"))]
|
||||
#:final (and (string=? book "Story")
|
||||
(string=? chapter "Conclusion")))
|
||||
(tuple book chapter))
|
||||
: (Listof (Tuple String String))
|
||||
-> (list (tuple "Guide" "Intro")
|
||||
(tuple "Guide" "Details")
|
||||
(tuple "Guide" "Conclusion")
|
||||
(tuple "Story" "Intro")
|
||||
(tuple "Story" "Details")
|
||||
(tuple "Story" "Conclusion")))
|
||||
|
||||
(check-type (for/list ([book (in-list (list "Guide" "Story" "Reference"))]
|
||||
#:final (string=? book "Story")
|
||||
[chapter (in-list (list "Intro" "Details" "Conclusion"))])
|
||||
(tuple book chapter))
|
||||
: (Listof (Tuple String String))
|
||||
-> (list (tuple "Guide" "Intro")
|
||||
(tuple "Guide" "Details")
|
||||
(tuple "Guide" "Conclusion")
|
||||
(tuple "Story" "Intro")))
|
||||
|
||||
;; ------------------------------------------------------------------------
|
||||
|
||||
;; Tests based on section 3.18 of the racket reference
|
||||
|
||||
(check-type (for/list ([i (in-list (list 1 2 3))]
|
||||
[j (in-list (list "a" "b" "c"))]
|
||||
#:when (odd? i)
|
||||
[k (in-list (list #t #f))])
|
||||
(tuple i j k))
|
||||
: (Listof (Tuple Int String Bool))
|
||||
-> (list (tuple 1 "a" #t)
|
||||
(tuple 1 "a" #f)
|
||||
(tuple 3 "c" #t)
|
||||
(tuple 3 "c" #f)))
|
||||
|
||||
(check-type (for/list ([i (in-list (list 1 2 3))]
|
||||
[j (in-list (list "a" "b" "c"))]
|
||||
#:break (not (odd? i))
|
||||
[k (in-list (list #t #f))])
|
||||
(tuple i j k))
|
||||
: (Listof (Tuple Int String Bool))
|
||||
-> (list (tuple 1 "a" #true)
|
||||
(tuple 1 "a" #false)))
|
||||
|
||||
(check-type (for/list ([i (in-list (list 1 2 3))]
|
||||
[j (in-list (list "a" "b" "c"))]
|
||||
#:final (not (odd? i))
|
||||
[k (in-list (list #t #f))])
|
||||
(tuple i j k))
|
||||
: (Listof (Tuple Int String Bool))
|
||||
-> (list (tuple 1 "a" #true)
|
||||
(tuple 1 "a" #false)
|
||||
(tuple 2 "b" #true)))
|
||||
|
|
@ -234,6 +234,66 @@ A @racket[syntax-parse]-like form that supports
|
|||
@racket[define-typed-syntax]-style clauses. In particular, see the
|
||||
"rule" part of @racket[define-typed-syntax]'s grammar above.}
|
||||
|
||||
@; ~typecheck and ~⊢
|
||||
|
||||
@defform[(~typecheck premise ...)]{
|
||||
A @racket[syntax-parse] @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")]{pattern expander}
|
||||
that supports typechecking syntax.
|
||||
|
||||
For example the pattern
|
||||
|
||||
@racketblock[
|
||||
(~typecheck
|
||||
[⊢ a ≫ a- ⇒ τ_a]
|
||||
[⊢ b ≫ b- ⇐ τ_a])]
|
||||
|
||||
typechecks @racket[a] and @racket[b], expecting @racket[b] to have the
|
||||
type of @racket[a], and binding @racket[a-] and @racket[b-] to the
|
||||
expanded versions.
|
||||
|
||||
This is most useful in places where you want to do typechecking in
|
||||
something other than a type rule, like in a function or a syntax
|
||||
class.
|
||||
|
||||
@(let ([ev (make-base-eval)])
|
||||
(ev '(require turnstile/turnstile))
|
||||
@examples[
|
||||
#:eval ev
|
||||
(begin-for-syntax
|
||||
(struct clause [stx- result-type])
|
||||
(code:comment "f : Stx -> Clause")
|
||||
(define (f stx)
|
||||
(syntax-parse stx
|
||||
[(~and [condition:expr body:expr]
|
||||
(~typecheck
|
||||
[⊢ condition ≫ condition- ⇐ Bool]
|
||||
[⊢ body ≫ body- ⇒ τ_body]))
|
||||
(clause #'[condition- body-] #'τ_body)])))
|
||||
])}
|
||||
|
||||
@defform*[[(~⊢ tc ...)]]{
|
||||
A shorthand @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")]{pattern expander}
|
||||
for @racket[(~typcheck [⊢ tc ...])].
|
||||
|
||||
For example the pattern @racket[(~⊢ a ≫ a- ⇒ τ_a)] typechecks
|
||||
@racket[a], binding the expanded version to @racket[a-] and the type
|
||||
to @racket[τ_a].
|
||||
|
||||
@(let ([ev (make-base-eval)])
|
||||
(ev '(require turnstile/turnstile))
|
||||
@examples[
|
||||
#:eval ev
|
||||
(begin-for-syntax
|
||||
(struct clause [stx- result-type])
|
||||
(code:comment "f : Stx -> Clause")
|
||||
(define (f stx)
|
||||
(syntax-parse stx
|
||||
[(~and [condition:expr body:expr]
|
||||
(~⊢ condition ≫ condition- ⇐ Bool)
|
||||
(~⊢ body ≫ body- ⇒ τ_body))
|
||||
(clause #'[condition- body-] #'τ_body)])))
|
||||
])}
|
||||
|
||||
@; define-primop --------------------------------------------------------------
|
||||
@defform*[((define-primop typed-op-id τ)
|
||||
(define-primop typed-op-id : τ)
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
(rename-out [define-typed-syntax define-typerule]
|
||||
[define-typed-syntax define-syntax/typecheck])
|
||||
(for-syntax syntax-parse/typecheck
|
||||
~typecheck ~⊢
|
||||
(rename-out
|
||||
[syntax-parse/typecheck syntax-parse/typed-syntax])))
|
||||
|
||||
|
@ -403,6 +404,17 @@
|
|||
(for-meta 2 'syntax-classes))
|
||||
|
||||
(begin-for-syntax
|
||||
(define-syntax ~typecheck
|
||||
(pattern-expander
|
||||
(syntax-parser
|
||||
[(_ clause:clause ...)
|
||||
#'(~and clause.pat ...)])))
|
||||
(define-syntax ~⊢
|
||||
(pattern-expander
|
||||
(syntax-parser
|
||||
[(_ . stuff)
|
||||
#'(~typecheck [⊢ . stuff])])))
|
||||
|
||||
(define-syntax syntax-parse/typecheck
|
||||
(syntax-parser
|
||||
[(_ stx-expr
|
||||
|
|
Loading…
Reference in New Issue
Block a user