Added record types where the order of fields is irrelevant.
This commit is contained in:
parent
6ad729b1ef
commit
61c290d1ec
8
aliases.rkt
Normal file
8
aliases.rkt
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#lang racket
|
||||||
|
(provide … ∘)
|
||||||
|
|
||||||
|
(require (only-in (only-meta-in 0 racket/base)
|
||||||
|
[... …]
|
||||||
|
[compose ∘])
|
||||||
|
racket/syntax)
|
||||||
|
|
10
info.rkt
10
info.rkt
|
@ -1,9 +1,15 @@
|
||||||
#lang info
|
#lang info
|
||||||
(define collection "phc-ts")
|
(define collection "phc-ts")
|
||||||
(define deps '("base" ;; ("base" "6.4")
|
(define deps '("base" ;; ("base" "6.4")
|
||||||
"rackunit-lib"))
|
"rackunit-lib"
|
||||||
|
"reprovide-lang"
|
||||||
|
"dotlambda"
|
||||||
|
"hyper-literate"
|
||||||
|
"phc-toolkit"
|
||||||
|
"turnstile"))
|
||||||
(define build-deps '("scribble-lib"
|
(define build-deps '("scribble-lib"
|
||||||
"racket-doc"))
|
"racket-doc"
|
||||||
|
"scribble-enhanced"))
|
||||||
(define scribblings '(("scribblings/phc-ts.scrbl" ())))
|
(define scribblings '(("scribblings/phc-ts.scrbl" ())))
|
||||||
(define pkg-desc "")
|
(define pkg-desc "")
|
||||||
(define version "0.0")
|
(define version "0.0")
|
||||||
|
|
131
main.rkt
131
main.rkt
|
@ -1,14 +1,129 @@
|
||||||
#lang hyper-literate #:no-auto-require(dotlambda/unhygienic . typed/racket/base)
|
#lang hyper-literate #:no-auto-require (dotlambda/unhygienic . turnstile/lang)
|
||||||
|
|
||||||
@(require (for-label typed/racket/base))
|
@(define (turnstile) @racketmodname[turnstile])
|
||||||
|
|
||||||
@section{Conclusion}
|
@(require racket/require
|
||||||
|
(for-label (prefix-in host: (only-meta-in 0 turnstile/lang))
|
||||||
|
(prefix-in host:
|
||||||
|
(subtract-in (only-meta-in 1 turnstile/lang)
|
||||||
|
(only-meta-in 0 turnstile/lang)))
|
||||||
|
turnstile/examples/mlish))
|
||||||
|
|
||||||
|
@section{Introduction}
|
||||||
|
|
||||||
|
We define our type system as an extension to another language. We could extend
|
||||||
|
one of the many sufficiently capable languages built with @turnstile[]. Here,
|
||||||
|
we will base ourselves on @racketmodname[mlish], a ml-like language
|
||||||
|
implemented with @turnstile[], and provided as part of @turnstile[]'s suite of
|
||||||
|
examples.
|
||||||
|
|
||||||
@chunk[<*>
|
@chunk[<*>
|
||||||
(provide
|
(extends turnstile/examples/mlish)]
|
||||||
;; description
|
|
||||||
#;id)
|
|
||||||
|
|
||||||
(require (for-syntax racket/base))
|
Since @racketmodname[turnstile/examples/mlish] provides some identifiers which
|
||||||
|
conflict with some racket utilities, we import those with a prefix.
|
||||||
|
|
||||||
#;(define id …)]
|
@chunk[<*>
|
||||||
|
(require racket/require
|
||||||
|
(prefix-in host: turnstile/lang)
|
||||||
|
(for-syntax "aliases.rkt")
|
||||||
|
(for-syntax "stx-sort.rkt")
|
||||||
|
(for-meta 2 syntax/stx)
|
||||||
|
(for-meta 2 "stx-sort.rkt")
|
||||||
|
(for-meta 2 "aliases.rkt"))]
|
||||||
|
|
||||||
|
We define a @racket[Record] type, in which the order of fields is irrelevant.
|
||||||
|
|
||||||
|
@CHUNK[<*>
|
||||||
|
;;(host:provide (type-out Record))
|
||||||
|
(host:provide Record (for-syntax ~Record))
|
||||||
|
|
||||||
|
(define-type-constructor Record* #:arity >= 0
|
||||||
|
#:arg-variances λstx.(make-list (sub1 (stx-length stx)) covariant))
|
||||||
|
|
||||||
|
(define-type-constructor Field* #:arity = 2
|
||||||
|
#:arg-variances λstx.(make-list (sub1 (stx-length stx)) covariant))
|
||||||
|
|
||||||
|
(define-syntax (Field stx)
|
||||||
|
(syntax-case stx ()
|
||||||
|
[(_ ℓ τ) #`(Field* #,(mk-type #'(quote ℓ)) τ)]))
|
||||||
|
|
||||||
|
(define-syntax Record
|
||||||
|
(syntax-parser
|
||||||
|
[(_ {~sort-seq [{~key ℓ:id} {~literal :} τ] …})
|
||||||
|
#'(Record* [Field ℓ τ] …)]))
|
||||||
|
|
||||||
|
(begin-for-syntax
|
||||||
|
(define-syntax ~Field
|
||||||
|
(pattern-expander
|
||||||
|
(λ (stx)
|
||||||
|
(syntax-case stx ()
|
||||||
|
[(_ ℓ τ)
|
||||||
|
#'[~Field* ({~literal quote} ℓ) τ]]))))
|
||||||
|
|
||||||
|
(define-syntax ~Record
|
||||||
|
(pattern-expander
|
||||||
|
(syntax-parser
|
||||||
|
[(_ [ℓ {~literal :} τ] {~datum ⊤ρ}) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
#'(~Record* _ (… …) [~Field ℓ τ] _ (… …))]
|
||||||
|
[(_ {~sort-seq [{~key ℓ} {~literal :} τ] …})
|
||||||
|
#'(~Record* [~Field ℓ τ] …)]))))]
|
||||||
|
|
||||||
|
The builder form for the @racket[Record] type is @racket[record].
|
||||||
|
|
||||||
|
@chunk[<*>
|
||||||
|
(host:provide record)
|
||||||
|
(define-typed-syntax
|
||||||
|
(record [ℓ:id {~literal =} e] …) ≫
|
||||||
|
[⊢ e ≫ e- ⇒ τ] …
|
||||||
|
#:with (tmp …) (generate-temporaries #'(e …))
|
||||||
|
#:with (~sort [{~key sorted-ℓ} sorted-τ sorted-tmp] …) #'([ℓ τ tmp] …)
|
||||||
|
--------
|
||||||
|
[⊢ (let ([tmp e] …)
|
||||||
|
(list- (list- 'sorted-ℓ sorted-tmp) …))
|
||||||
|
⇒ (Record [sorted-ℓ : sorted-τ] …)])]
|
||||||
|
|
||||||
|
Fields can be accessed via the @racket[getfield] operator. The @racket[i.ℓ]
|
||||||
|
notation will later be introduced, and will reduce to @racket[(getfield i ℓ)].
|
||||||
|
|
||||||
|
@chunk[<*>
|
||||||
|
(host:provide getfield)
|
||||||
|
(require (for-syntax "same-id-pattern.rkt"))
|
||||||
|
(define-typed-syntax
|
||||||
|
(getfield ℓ:id e) ≫
|
||||||
|
[⊢ e ≫ e- ⇒ {~or {~Record [{~same-free-id ℓ} : τ] ⊤ρ} <record-err-ℓ>}]
|
||||||
|
--------
|
||||||
|
[⊢ (car- (assoc- 'ℓ e-)) ⇒ τ])]
|
||||||
|
|
||||||
|
@chunk[<record-err-ℓ>
|
||||||
|
{~and
|
||||||
|
other
|
||||||
|
{~do (type-error
|
||||||
|
#:src #'other
|
||||||
|
#:msg "expected record type containing the field ~a, got: ~a"
|
||||||
|
#'ℓ #'other)}}]
|
||||||
|
|
||||||
|
@chunk[<*>
|
||||||
|
(host:provide :)
|
||||||
|
(define-syntax (: stx)
|
||||||
|
(raise-syntax-error ': "Invalid use of token" stx))]
|
||||||
|
|
||||||
|
We define a quick and dirty @racket[:type] operator, which can be used to
|
||||||
|
print the type of an expression. For now, we simply trigger an error message
|
||||||
|
which should contain the inferred type, unless the type of @racket[e] is an
|
||||||
|
@racket[Int].
|
||||||
|
|
||||||
|
@chunk[<*>
|
||||||
|
(host:provide :type)
|
||||||
|
(require syntax/macro-testing)
|
||||||
|
(define ann/:type-regexp
|
||||||
|
#px"ann: type mismatch: expected Int, given (.*)\n expression:")
|
||||||
|
(define-syntax-rule (:type e)
|
||||||
|
(with-handlers ([(λ (exn) #true)
|
||||||
|
(λ (exn)
|
||||||
|
(displayln
|
||||||
|
(cadr (regexp-match ann/:type-regexp
|
||||||
|
(exn-message exn)))))])
|
||||||
|
(convert-compile-time-error (ann e : Int))))]
|
||||||
|
|
||||||
|
@section{Conclusion}
|
||||||
|
|
15
same-id-pattern.rkt
Normal file
15
same-id-pattern.rkt
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#lang racket
|
||||||
|
(provide ~same-free-id)
|
||||||
|
|
||||||
|
(require syntax/parse)
|
||||||
|
|
||||||
|
(define-splicing-syntax-class (same-free-id f)
|
||||||
|
#:description (format "the identifier ~a"
|
||||||
|
(syntax-e f))
|
||||||
|
(pattern x #:when (and (identifier? #'x) (free-identifier=? #'x f))))
|
||||||
|
|
||||||
|
(define-syntax ~same-free-id
|
||||||
|
(pattern-expander
|
||||||
|
(λ (stx)
|
||||||
|
(syntax-case stx ()
|
||||||
|
[(_ pvar) (identifier? #'pvar) #'{~var || (same-free-id #'pvar)}]))))
|
33
scribblings/phc-ts.scrbl
Normal file
33
scribblings/phc-ts.scrbl
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
#lang scribble/manual
|
||||||
|
@require[@for-label[phc-ts]
|
||||||
|
(for-syntax racket/base)]
|
||||||
|
|
||||||
|
@title{phc-ts}
|
||||||
|
@author[@author+email["Georges Dupéron" "georges.duperon@gmail.com"]]
|
||||||
|
|
||||||
|
@defmodule[phc-ts]
|
||||||
|
|
||||||
|
There is no documentation for this package yet.
|
||||||
|
|
||||||
|
@(define-syntax (show-ids stx)
|
||||||
|
(syntax-case stx ()
|
||||||
|
[(_ b)
|
||||||
|
(boolean? (syntax-e #'b))
|
||||||
|
(let-values ([(vars stx-vars) (module->exports 'phc-ts)])
|
||||||
|
#`(itemlist
|
||||||
|
#,@(for*/list ([phase+ids (in-list (if (syntax-e #'b)
|
||||||
|
vars
|
||||||
|
stx-vars))]
|
||||||
|
[phase (in-value (car phase+ids))]
|
||||||
|
[id (in-list (cdr phase+ids))])
|
||||||
|
#`(item (racketid #,id)
|
||||||
|
"at phase"
|
||||||
|
#,(number->string phase)))))]))
|
||||||
|
|
||||||
|
The following variables are provided:
|
||||||
|
|
||||||
|
@(show-ids #t)
|
||||||
|
|
||||||
|
The following syntaxes are provided:
|
||||||
|
|
||||||
|
@(show-ids #f)
|
|
@ -1,31 +0,0 @@
|
||||||
#lang scribble/manual
|
|
||||||
@require[@for-label[phc-ts
|
|
||||||
racket/base]]
|
|
||||||
|
|
||||||
@title{phc-ts}
|
|
||||||
@author[@author+email["Georges Dupéron" "georges.duperon@gmail.com"]]
|
|
||||||
|
|
||||||
@defmodule[phc-ts]
|
|
||||||
|
|
||||||
There is no documentation for this package yet.
|
|
||||||
|
|
||||||
@(define-syntax (show-ids _stx)
|
|
||||||
(syntax-case stx ()
|
|
||||||
[(_ b)
|
|
||||||
(boolean? (syntax-e #'b))
|
|
||||||
(let-values ([(vars stx-vars) (module->exports phc-ts)])
|
|
||||||
#`(itemlist
|
|
||||||
#,(for*/list ([phase+ids (in-list (if (syntax-e #'b) vars stx-vars))]
|
|
||||||
[phase (in-value (car phase+ids))]
|
|
||||||
[id (in-list (cdr phase+ids))])
|
|
||||||
#`(item (racketit #,id)
|
|
||||||
"at phase"
|
|
||||||
#,(number->string phase)))))]))
|
|
||||||
|
|
||||||
The following variables are provided:
|
|
||||||
|
|
||||||
@(show-ids #t)
|
|
||||||
|
|
||||||
The following syntaxes are provided:
|
|
||||||
|
|
||||||
@(show-ids #f)
|
|
84
stx-sort.rkt
Normal file
84
stx-sort.rkt
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
#lang racket
|
||||||
|
|
||||||
|
(provide ~sort
|
||||||
|
~sort-seq
|
||||||
|
~key)
|
||||||
|
|
||||||
|
;; Note: when using nested ~sort, the inner sort is not performed during the
|
||||||
|
;; first pass for the outer ~sort. Once the values for the outer ~sort have been
|
||||||
|
;; gathered and sorted, then the innder ~sort is applied. This means that the
|
||||||
|
;; comparison operator for the outer ~sort should work with unsorted
|
||||||
|
;; sub-sequences.s
|
||||||
|
|
||||||
|
(require syntax/parse
|
||||||
|
"aliases.rkt"
|
||||||
|
syntax/stx
|
||||||
|
racket/stxparam
|
||||||
|
(for-syntax racket/syntax))
|
||||||
|
|
||||||
|
(define-for-syntax sort-scope (make-syntax-introducer))
|
||||||
|
(define-syntax-parameter current-key-id #f)
|
||||||
|
|
||||||
|
(define-for-syntax (~sort-ish op*)
|
||||||
|
(pattern-expander
|
||||||
|
(λ (stx)
|
||||||
|
(syntax-case stx (…)
|
||||||
|
[(self pat …)
|
||||||
|
(if (syntax-parameter-value #'current-key-id)
|
||||||
|
#`(#,@op* _ …)
|
||||||
|
#`(~and (#,@op* tmp …)
|
||||||
|
(~parse (pat …)
|
||||||
|
(sort/stx self #'(tmp …) pat))))]))))
|
||||||
|
(define-syntax ~sort (~sort-ish #'{}))
|
||||||
|
|
||||||
|
(define-syntax ~sort-seq (~sort-ish #'{~seq}))
|
||||||
|
|
||||||
|
(define-syntax (sort/stx stx)
|
||||||
|
(syntax-case stx ()
|
||||||
|
[(_ ctx stxlist pat)
|
||||||
|
#'(syntax-parameterize
|
||||||
|
([current-key-id (generate-temporary #'key)])
|
||||||
|
(def-cls tmpcls pat)
|
||||||
|
(and (syntax-parse stxlist [({~var || tmpcls} …) #t] [_ (displayln (format "Failed to parse ~a as ~a." stxlist 'pat)) #f])
|
||||||
|
(sort (syntax->list stxlist)
|
||||||
|
(λ (a b)
|
||||||
|
(cond
|
||||||
|
[(and (symbol? a) (symbol? b)) (symbol<? a b)]
|
||||||
|
[(and (number? a) (number? b)) (< a b)]
|
||||||
|
[else (number? a)])) ; numbers come first in the order
|
||||||
|
#:key (do-parse tmpcls))))]))
|
||||||
|
|
||||||
|
(define-syntax (def-cls stx)
|
||||||
|
(syntax-case stx ()
|
||||||
|
[(_ tmpcls pat)
|
||||||
|
(with-syntax ([key (syntax-parameter-value
|
||||||
|
#'current-key-id)])
|
||||||
|
#'(define-syntax-class tmpcls
|
||||||
|
;; I can't seem to manage to establish reliable communication between
|
||||||
|
;; the ~sort and the ~key. So here we're relying on the fact that
|
||||||
|
;; #:attributes is actually unhygienic, in order to get a handle on
|
||||||
|
;; the key as defined by ~key.
|
||||||
|
#:attributes (key)
|
||||||
|
(pattern pat)))]))
|
||||||
|
|
||||||
|
(define-syntax (do-parse stx)
|
||||||
|
(syntax-case stx ()
|
||||||
|
[(_ tmpcls)
|
||||||
|
(with-syntax ([x.key (format-id #'x "x.~a" (syntax-parameter-value
|
||||||
|
#'current-key-id))])
|
||||||
|
#'(syntax-parser
|
||||||
|
[(~var x tmpcls) (syntax-e #'x.key)]))]))
|
||||||
|
|
||||||
|
(define-syntax ~key
|
||||||
|
(pattern-expander
|
||||||
|
(λ (stx)
|
||||||
|
(syntax-case stx ()
|
||||||
|
[(self pat)
|
||||||
|
(if (syntax-parameter-value #'current-key-id)
|
||||||
|
(with-syntax ([key (syntax-parameter-value #'current-key-id)])
|
||||||
|
#`(~and pat key))
|
||||||
|
#'(~and pat _))]))))
|
||||||
|
|
||||||
|
#;(syntax-parse #'([a 3] [c 1] [b 2])
|
||||||
|
[{~sort [{~key k} v] …}
|
||||||
|
#'([k . v] …)])
|
12
test/same-id-pattern-test.rkt
Normal file
12
test/same-id-pattern-test.rkt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#lang racket
|
||||||
|
(require syntax/parse
|
||||||
|
phc-ts/same-id-pattern
|
||||||
|
rackunit)
|
||||||
|
|
||||||
|
(check-true (syntax-parse #'(a 1 2 a 4)
|
||||||
|
[(y _ ... {~same-free-id y} _ ...) #t]
|
||||||
|
[_ #f]))
|
||||||
|
|
||||||
|
(check-false (syntax-parse #'(a 1 2 b 4)
|
||||||
|
[(y _ ... {~same-free-id y} _ ...) #t]
|
||||||
|
[_ #f]))
|
20
test/stx-sort-test.rkt
Normal file
20
test/stx-sort-test.rkt
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#lang racket
|
||||||
|
(require phc-ts/stx-sort
|
||||||
|
syntax/parse
|
||||||
|
phc-toolkit/untyped/aliases
|
||||||
|
rackunit)
|
||||||
|
|
||||||
|
(check-equal? (syntax-parse #'([a 3] [c 2] [b 1])
|
||||||
|
[{~sort [{~key k} v] …}
|
||||||
|
(syntax->datum #'([k . v] …))])
|
||||||
|
'([a . 3] [b . 1] [c . 2]))
|
||||||
|
|
||||||
|
(check-equal? (syntax-parse #'([a z y] [c x] [b w])
|
||||||
|
[{~sort [{~key k} . {~sort {~key v} …}] …}
|
||||||
|
(syntax->datum #'([k v …] …))])
|
||||||
|
'([a y z] [b w] [c x]))
|
||||||
|
|
||||||
|
(check-equal? (syntax-parse #'([a 5 4] [c 3 1 2] [b 0])
|
||||||
|
[{~sort [{~key k} . {~sort {~key v} …}] …}
|
||||||
|
(syntax->datum #'([k v …] …))])
|
||||||
|
'([a 4 5] [b 0] [c 1 2 3]))
|
12
test/test1.rkt
Normal file
12
test/test1.rkt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#lang s-exp phc-ts
|
||||||
|
(require turnstile/examples/tests/rackunit-typechecking)
|
||||||
|
|
||||||
|
(check-type (λ ([x : X]) (ann x : X)) : (→/test A A))
|
||||||
|
(check-type (λ ([x : y]) (ann x : y)) : (→/test A A))
|
||||||
|
(check-type (λ ([x : y]) x) : (→/test A A))
|
||||||
|
(check-type (record [b = 1] [a = 2]) : (Record [a : Int] [b : Int]))
|
||||||
|
(check-type (record [a = 2] [b = 1]) : (Record [a : Int] [b : Int]))
|
||||||
|
(typecheck-fail
|
||||||
|
(getfield c (record [b = 1] [a = 2]))
|
||||||
|
#:with-msg (string-append "expected record type containing the field c, got:"
|
||||||
|
" \\(Record \\(a : Int\\) \\(b : Int\\)\\)"))
|
Loading…
Reference in New Issue
Block a user