syndicate-2017/racket/typed/roles.rkt

2101 lines
81 KiB
Racket
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#lang turnstile
(provide #%module-begin
(rename-out [typed-app #%app])
(rename-out [typed-quote quote])
#%top-interaction
require only-in
;; Start dataspace programs
run-ground-dataspace
;; Types
Int Bool String Tuple Bind Discard List ByteString Symbol
Role Reacts Shares Know ¬Know Message OnDataflow Stop OnStart OnStop
FacetName Field ★/t
Observe Inbound Outbound Actor U
Computation Value Endpoints Roles Spawns
;; Statements
let let* if spawn dataspace start-facet set! begin stop begin/dataflow #;unsafe-do
when unless send!
;; Derived Forms
during define/query-value define/query-set
;; endpoints
assert on field
;; expressions
tuple select lambda ref observe inbound outbound
;; making types
define-type-alias
define-constructor define-constructor*
;; values
#%datum
;; patterns
bind discard
;; primitives
+ - * / and or not > < >= <= = equal? displayln printf define
gensym symbol->string string->symbol bytes->string/utf-8 string->bytes/utf-8
~a
;; lists
list cons first rest member? empty? for for/fold
;; sets
Set set set-member? set-add set-remove set-count set-union set-subtract set-intersect
list->set set->list
;; DEBUG and utilities
print-type print-role
;; Extensions
match cond
;; require & provides
require provide
submod for-syntax for-meta only-in except-in
require/typed
require-struct
)
(require (only-in turnstile
[define-type-constructor define-type-constructor-]))
(require (prefix-in syndicate: syndicate/actor-lang))
(require (for-meta 2 macrotypes/stx-utils racket/list syntax/stx syntax/parse racket/base))
(require (for-syntax turnstile/examples/util/filter-maximal))
(require (for-syntax racket/struct-info))
(require macrotypes/postfix-in)
(require (rename-in racket/math [exact-truncate exact-truncate-]))
(require (postfix-in - racket/list))
(require (postfix-in - racket/set))
(require (postfix-in - racket/match))
(require (postfix-in - (only-in racket/format ~a)))
(module+ test
(require rackunit)
(require rackunit/turnstile))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Debugging
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-for-syntax DEBUG-BINDINGS? #f)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Type Checking Conventions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; : describes the immediate result of evaluation
;; ν-ep key aggregates endpoint affects:
;; `Shares`, `Reacts`, and `MakesField`
;; Note thar MakesField is only an effect, not a type
;; ν-f key aggregates facet effects (starting/stopping a facet) as `Role`s & `Stop`s and message sends, `Sends`
;; ν-s key aggregates spawned actors as `Actor`s
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Types
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; certain metadata needs to be associated with each type, for the purpose of
;; making certain judgments and metafunctions extensible.
;; a isect-desc describes how a type (constructor) behaves with respect to
;; intersection, and is one of
;; - BASE
;; - CONTAINER-LIKE
;; - PRODUCT-LIKE
(begin-for-syntax
(define BASE 'base)
(define CONTAINER-LIKE 'container-like)
(define PRODUCT-LIKE 'product-like)
;; syntax property key
(define isect-desc-key
'isect-desc-key)
(define-syntax-class isect-desc
#:attributes (val)
#:datum-literals (BASE CONTAINER-LIKE PRODUCT-LIKE)
(pattern BASE
#:attr val BASE)
(pattern CONTAINER-LIKE
#:attr val CONTAINER-LIKE)
(pattern PRODUCT-LIKE
#:attr val PRODUCT-LIKE))
;; Any -> Bool
;; recognize isect-descs
(define (isect-desc? x)
(member x (list BASE CONTAINER-LIKE PRODUCT-LIKE)))
;; syntax property key
;; syntax-transformer value
(define type-cons-key
'type-cons)
;; Type -> Bool
;; check if the type has a syntax property allowing us to create new instances
(define (reassemblable? t)
(and (syntax-property t type-cons-key) #t))
;; Type (Listof Type) -> Type
;; Create a new instance of the type with the given arguments
;; needs to have the type-cons-key
(define (reassemble-type ty args)
(define tycons (syntax-property ty type-cons-key))
(unless tycons
(error "expected to find type-cons-key"))
(type-eval #`(#,tycons #,@args))))
(define-syntax (define-type-constructor+ stx)
(syntax-parse stx
[(_ Name:id
#:arity op arity
#:arg-variances variances
#:isect-desc desc:isect-desc
(~optional (~seq #:extra-info extra-info)))
#:with Name- (mk-- #'Name)
#:with NamePat (mk-~ #'Name)
#:with NamePat- (mk-~ #'Name-)
#:with mk (format-id #'Name "mk-~a-" (syntax-e #'Name))
#:with mk- (format-id #'Name- "mk-~a-" (syntax-e #'Name-))
(quasisyntax/loc stx
(begin-
(define-type-constructor- Name-
#:arity op arity
#:arg-variances variances
#,@(if (attribute extra-info)
#'(#:extra-info extra-info)
#'()))
(define-syntax (Name stx)
(syntax-parse stx
[(_ t (... ...))
(set-stx-prop/preserved
(set-stx-prop/preserved
(syntax/loc stx
(Name- t (... ...)))
#,isect-desc-key
'#,(attribute desc.val))
type-cons-key
#'Name)]))
(begin-for-syntax
(define-syntax NamePat
(pattern-expander
(syntax-parser
[(_ p (... ...))
#'(NamePat- p (... ...))]))))
(define-for-syntax mk mk-)))]))
(begin-for-syntax
;; Syntax -> (Listof Variant)
;; make a list of the same length as the number of arguments of the given
;; (type) syntax, all covariant
(define (mk-covariant ts)
(for/list ([_ (sequence-tail (in-syntax ts) 1)])
covariant)))
;; Define a type constructor that acts like a container:
;; - covariant
;; - has an empty element (i.e. intersection always non-empty)
(define-syntax (define-container-type stx)
(syntax-parse stx
[(_ Name:id #:arity op arity
(~optional (~seq #:extra-info extra-info)))
(quasisyntax/loc stx
(define-type-constructor+ Name
#:arity op arity
#:arg-variances mk-covariant
#:isect-desc CONTAINER-LIKE
#,@(if (attribute extra-info)
#'(#:extra-info extra-info)
#'())))]))
;; Define a type constructor that acts like a container:
;; - covariant
;; - does not have an empty element (i.e. intersection may be empty)
(define-syntax (define-product-type stx)
(syntax-parse stx
[(_ Name:id #:arity op arity
(~optional (~seq #:extra-info extra-info)))
(quasisyntax/loc stx
(define-type-constructor+ Name
#:arity op arity
#:arg-variances mk-covariant
#:isect-desc PRODUCT-LIKE
#,@(if (attribute extra-info)
#'(#:extra-info extra-info)
#'())))]))
(define-binding-type Role #:arity >= 0 #:bvs = 1)
(define-type-constructor Shares #:arity = 1)
(define-type-constructor Sends #:arity = 1)
(define-type-constructor Reacts #:arity >= 1)
(define-type-constructor Know #:arity = 1)
(define-type-constructor ¬Know #:arity = 1)
(define-type-constructor Stop #:arity >= 1)
(define-type-constructor Field #:arity = 1)
(define-type-constructor Bind #:arity = 1)
;; keep track of branches for facet effects
;; (Branch (Listof (Listof Type)))
(define-type-constructor Branch #:arity >= 0)
;; sequence of effects
(define-type-constructor Effs #:arity >= 0)
(define-base-types OnStart OnStop OnDataflow MakesField)
(define-for-syntax field-prop-name 'fields)
(define-type-constructor Actor #:arity = 1)
(define-product-type Message #:arity = 1)
(define-product-type Tuple #:arity >= 0)
(define-product-type Observe #:arity = 1)
(define-product-type Inbound #:arity = 1)
(define-product-type Outbound #:arity = 1)
(define-container-type AssertionSet #:arity = 1)
(define-container-type Patch #:arity = 2)
(define-container-type List #:arity = 1)
(define-container-type Set #:arity = 1)
(define-type-constructor #:arity > 0)
;; for describing the RHS
;; a value and a description of the effects
(define-type-constructor Computation #:arity = 4)
(define-type-constructor Value #:arity = 1)
(define-type-constructor Endpoints #:arity >= 0)
(define-type-constructor Roles #:arity >= 0)
(define-type-constructor Spawns #:arity >= 0)
(define-base-types Int Bool String Discard ★/t FacetName ByteString Symbol)
(define-for-syntax (type-eval t)
((current-type-eval) t))
(define-type-constructor U* #:arity >= 0)
;; τ.norm in 1st case causes "not valid type" error when referring to ⊥ in another file.
;; however, this version expands the type at every reference, incurring a potentially large
;; overhead---2x in the case of book-club.rkt
;; (copied from ext-stlc example)
(define-syntax define-type-alias
(syntax-parser
[(_ alias:id τ)
#'(define-syntax- alias
(make-variable-like-transformer #'τ))]
[(_ (f:id x:id ...) ty)
#'(define-syntax- (f stx)
(syntax-parse stx
[(_ x ...)
#:with τ:any-type #'ty
#'τ.norm]))]))
(define-type-alias (U*))
(define-for-syntax (prune+sort tys)
(stx-sort
(filter-maximal
(stx->list tys)
typecheck?)))
(define-syntax (U stx)
(syntax-parse stx
[(_ . tys)
;; canonicalize by expanding to U*, with only (sorted and pruned) leaf tys
#:with ((~or (~U* ty1- ...) ty2-) ...) (stx-map (current-type-eval) #'tys)
#:with tys- (prune+sort #'(ty1- ... ... ty2- ...))
(if (= 1 (stx-length #'tys-))
(stx-car #'tys-)
(syntax/loc stx (U* . tys-)))]))
;; for looking at the "effects"
(begin-for-syntax
(define-syntax ~effs
(pattern-expander
(syntax-parser
[(_ eff:id ...)
#:with tmp (generate-temporary 'effss)
#'(~and tmp
(~parse (eff ...) (stx-or #'tmp #'())))])))
(define (stx-truth? a)
(and a (not (and (syntax? a) (false? (syntax-e a))))))
(define (stx-or a b)
(cond [(stx-truth? a) a]
[else b])))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; User Defined Types, aka Constructors
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(begin-for-syntax
(define-splicing-syntax-class type-constructor-decl
(pattern (~seq #:type-constructor TypeCons:id))
(pattern (~seq) #:attr TypeCons #f))
(struct user-ctor (typed-ctor untyped-ctor)
#:property prop:procedure
(lambda (v stx)
(define transformer (user-ctor-typed-ctor v))
(syntax-parse stx
[(_ e ...)
(quasisyntax/loc stx
(#,transformer e ...))]))))
(define-syntax (define-constructor* stx)
(syntax-parse stx
#:datum-literals (:)
[(_ (Cons:id : TyCons:id slot:id ...) clause ...)
#'(define-constructor (Cons slot ...)
#:type-constructor TyCons
clause ...)]))
(define-syntax (define-constructor stx)
(syntax-parse stx
[(_ (Cons:id slot:id ...)
ty-cons:type-constructor-decl
(~seq #:with
Alias AliasBody) ...)
#:with TypeCons (or (attribute ty-cons.TypeCons) (format-id stx "~a/t" (syntax-e #'Cons)))
#:with MakeTypeCons (format-id #'TypeCons "make-~a" #'TypeCons)
#:with GetTypeParams (format-id #'TypeCons "get-~a-type-params" #'TypeCons)
#:with TypeConsExpander (format-id #'TypeCons "~~~a" #'TypeCons)
#:with TypeConsExtraInfo (format-id #'TypeCons "~a-extra-info" #'TypeCons)
#:with (StructName Cons- type-tag) (generate-temporaries #'(Cons Cons Cons))
(define arity (stx-length #'(slot ...)))
#`(begin-
(struct- StructName (slot ...) #:reflection-name 'Cons #:transparent)
(define-syntax (TypeConsExtraInfo stx)
(syntax-parse stx
[(_ X (... ...)) #'('type-tag 'MakeTypeCons 'GetTypeParams)]))
(define-product-type TypeCons
#:arity = #,arity
#:extra-info 'TypeConsExtraInfo)
(define-syntax (MakeTypeCons stx)
(syntax-parse stx
[(_ t (... ...))
#:fail-unless (= #,arity (stx-length #'(t (... ...)))) "arity mismatch"
#'(TypeCons t (... ...))]))
(define-syntax (GetTypeParams stx)
(syntax-parse stx
[(_ (TypeConsExpander t (... ...)))
#'(t (... ...))]))
(define-syntax Cons
(user-ctor #'Cons- #'StructName))
(define-typed-syntax (Cons- e (... ...))
#:fail-unless (= #,arity (stx-length #'(e (... ...)))) "arity mismatch"
[ e e- ( : τ)] (... ...)
#:fail-unless (all-pure? #'(e- (... ...))) "expressions must be pure"
----------------------
[ (#%app- StructName e- (... ...)) ( : (TypeCons τ (... ...)))])
(define-type-alias Alias AliasBody) ...)]))
;; (require-struct chicken #:as Chicken #:from "some-mod.rkt") will
;; - extract the struct-info for chicken, and ensure that it is immutable, has a set number of fields
;; - determine the number of slots, N, chicken has
;; - define the type constructor (Chicken ...N), with the extra info used by define-constructor above
;; - define chicken+, a turnstile type rule that checks uses of chicken
;; - bind chicken to a user-ctor struct
;; TODO: this implementation shares a lot with that of define-constructor
(define-syntax (require-struct stx)
(syntax-parse stx
[(_ ucons:id #:as ty-cons:id #:from lib)
(with-syntax* ([TypeCons #'ty-cons]
[MakeTypeCons (format-id #'TypeCons "make-~a" #'TypeCons)]
[GetTypeParams (format-id #'TypeCons "get-~a-type-params" #'TypeCons)]
[TypeConsExpander (format-id #'TypeCons "~~~a" #'TypeCons)]
[TypeConsExtraInfo (format-id #'TypeCons "~a-extra-info" #'TypeCons)]
[Cons- (format-id #'ucons "~a/checked" #'ucons)]
[orig-struct-info (generate-temporary #'ucons)]
[type-tag (generate-temporary #'ucons)])
(quasisyntax/loc stx
(begin-
(require- (only-in- lib [ucons orig-struct-info]))
(begin-for-syntax
(define info (syntax-local-value #'orig-struct-info))
(unless (struct-info? info)
(raise-syntax-error #f "expected struct" #'#,stx #'ucons))
(match-define (list desc cons pred accs muts sup) (extract-struct-info info))
(when (false? (last accs))
(raise-syntax-error #f "number of slots must be exact" #'#,stx #'ucons))
(unless (equal? #t sup)
(raise-syntax-error #f "structs with super-type not supported" #'#,stx #'ucons))
(define arity (length accs)))
(define-syntax (TypeConsExtraInfo stx)
(syntax-parse stx
[(_ X (... ...)) #'('type-tag 'MakeTypeCons 'GetTypeParams)]))
(define-type-constructor TypeCons
;; issue: arity needs to parse as an exact-nonnegative-integer
;; fix: check arity in MakeTypeCons
#:arity >= 0
#:extra-info 'TypeConsExtraInfo)
(define-syntax (MakeTypeCons stx)
(syntax-parse stx
[(_ t (... ...))
#:fail-unless (= arity (stx-length #'(t (... ...)))) "arity mismatch"
#'(TypeCons t (... ...))]))
(define-syntax (GetTypeParams stx)
(syntax-parse stx
[(_ (TypeConsExpander t (... ...)))
#'(t (... ...))]))
(define-typed-syntax (Cons- e (... ...))
#:fail-unless (= arity (stx-length #'(e (... ...)))) "arity mismatch"
[ e e- ( : τ)] (... ...)
#:fail-unless (all-pure? #'(e- (... ...))) "expressions must be pure"
----------------------
[ (#%app- orig-struct-info e- (... ...)) ( : (TypeCons τ (... ...)))])
(define-syntax ucons
(user-ctor #'Cons- #'orig-struct-info)))))]))
(begin-for-syntax
(define-syntax ~constructor-extra-info
(pattern-expander
(syntax-parser
[(_ tag mk get)
#'(_ (_ tag) (_ mk) (_ get))])))
(define-syntax ~constructor-type
(pattern-expander
(syntax-parser
[(_ tag . rst)
#'(~and it
(~fail #:unless (user-defined-type? #'it))
(~parse tag (get-type-tag #'it))
(~Any _ . rst))])))
(define-syntax ~constructor-exp
(pattern-expander
(syntax-parser
[(_ cons . rst)
#'(~and (cons . rst)
(~fail #:unless (ctor-id? #'cons)))])))
(define (inspect t)
(syntax-parse t
[(~constructor-type tag t ...)
(list (syntax-e #'tag) (stx-map type->str #'(t ...)))]))
(define (tags-equal? t1 t2)
(equal? (syntax-e t1) (syntax-e t2)))
(define (user-defined-type? t)
(get-extra-info (type-eval t)))
(define (get-type-tag t)
(syntax-parse (get-extra-info t)
[(~constructor-extra-info tag _ _)
(syntax-e #'tag)]))
(define (get-type-args t)
(syntax-parse (get-extra-info t)
[(~constructor-extra-info _ _ get)
(define f (syntax-local-value #'get))
(syntax->list (f #`(get #,t)))]))
(define (make-cons-type t args)
(syntax-parse (get-extra-info t)
[(~constructor-extra-info _ mk _)
(define f (syntax-local-value #'mk))
(type-eval (f #`(mk #,@args)))]))
(define (ctor-id? stx)
(and (identifier? stx)
(user-ctor? (syntax-local-value stx (const #f)))))
(define (untyped-ctor stx)
(user-ctor-untyped-ctor (syntax-local-value stx (const #f)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Require & Provide
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Import and ascribe a type from an untyped module
;; TODO: this is where contracts would need to go
(define-syntax (require/typed stx)
(syntax-parse stx
#:datum-literals (:)
[(_ lib [name:id : ty:type] ...)
#:with (name- ...) (format-ids "~a-" #'(name ...))
(syntax/loc stx
(begin-
(require (only-in lib [name name-] ...))
(define-syntax name
(make-variable-like-transformer (add-orig (assign-type #'name- #'ty #:wrap? #f) #'name)))
...))]))
;; Format identifiers in the same way
;; FormatString (SyntaxListOf Identifier) -> (Listof Identifier)
(define-for-syntax (format-ids fmt ids)
(for/list ([id (in-syntax ids)])
(format-id id fmt id)))
;; (SyntaxListof (SyntaxList Identifier Type Identifier)) -> (Listof Identifier)
;; For each triple (name- ty name),
;; assign the ty to name- with the orig name
(define-for-syntax (assign-types los)
(for/list ([iti (in-syntax los)])
(match-define (list name- ty name) (syntax->list iti))
(add-orig (assign-type name- ty #:wrap? #f) name)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Conveniences
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; (SyntaxListof (SyntaxListof Type)) -> (U (SyntaxListof Branch) #'())
(define-for-syntax (make-Branch tys*)
(syntax-parse tys*
[()
#'()]
[(() ...)
#'()]
[((ty ...) ...)
(define effs
(for/list ([tys (in-syntax tys*)])
(mk-Effs- (syntax->list tys))))
#`(#,(mk-Branch- effs))]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Syntax
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(begin-for-syntax
;; constructors with arity one
(define-syntax-class kons1
(pattern (~or (~datum observe)
(~datum inbound)
(~datum outbound)
(~datum message))))
(define (kons1->constructor stx)
(syntax-parse stx
#:datum-literals (observe inbound outbound)
[observe #'syndicate:observe]
[inbound #'syndicate:inbound]
[outbound #'syndicate:outbound]
[message #'syndicate:message]))
(define-syntax-class basic-val
(pattern (~or boolean
integer
string)))
(define-syntax-class prim-op
(pattern (~or (~literal +)
(~literal -)
(~literal displayln)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Utilities Over Types
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-for-syntax (bot? t)
((current-typecheck-relation) t (type-eval #'(U*))))
(define-for-syntax (flat-type? τ)
(syntax-parse τ
[(~→ τ ...) #f]
[(~Actor τ) #f]
[_ #t]))
(define-for-syntax (strip-? t)
(type-eval
(syntax-parse t
[(~U* τ ...) #`(U #,@(stx-map strip-? #'(τ ...)))]
[~★/t #'★/t]
;; since (Observe X) can match (Message X):
;; doing this specifically for the intersection operation in the spawn rule, need to check other
;; uses
[(~Observe τ) #'(U τ (Message τ))]
[_ #'(U*)])))
;; similar to strip- fns, but leave non-message types as they are
(define-for-syntax (prune-message t)
(type-eval
(syntax-parse t
[(~U* τ ...) #`(U #,@(stx-map prune-message #'(τ ...)))]
[~★/t #'★/t]
[(~Message τ) #'τ]
[_ t])))
(define-for-syntax (strip-inbound t)
(type-eval
(syntax-parse t
[(~U* τ ...) #`(U #,@(stx-map strip-inbound #'(τ ...)))]
[~★/t #'★/t]
[(~Inbound τ) #'τ]
[_ #'(U*)])))
(define-for-syntax (strip-outbound t)
(type-eval
(syntax-parse t
[(~U* τ ...) #`(U #,@(stx-map strip-outbound #'(τ ...)))]
[~★/t #'★/t]
[(~Outbound τ) #'τ]
[_ #'(U*)])))
(define-for-syntax (relay-interests t)
(type-eval
(syntax-parse t
;; TODO: probably need to `normalize` the result
[(~U* τ ...) #`(U #,@(stx-map relay-interests #'(τ ...)))]
[~★/t #'★/t]
[(~Observe (~Inbound τ)) #'(Observe τ)]
[_ #'(U*)])))
;; (SyntaxOf RoleType ...) -> (Syntaxof InputType OutputType SpawnType)
(define-for-syntax (analyze-roles rs)
(define-values (lis los lss)
(for/fold ([is '()]
[os '()]
[ss '()])
([r (in-syntax rs)])
(define-values (i o s) (analyze-role-input/output r))
(values (cons i is) (cons o os) (cons s ss))))
#`(#,(type-eval #`(U #,@lis))
#,(type-eval #`(U #,@los))
#,(type-eval #`(U #,@lss))))
;; Wanted test case, but can't use it bc it uses things defined for-syntax
#;(module+ test
(let ([r (type-eval #'(Role (x) (Shares Int)))])
(syntax-parse (analyze-role-input/output r)
[(τ-i τ-o)
(check-true (type=? #'τ-o (type-eval #'Int)))])))
;; RoleType -> (Values InputType OutputType SpawnType)
(define-for-syntax (analyze-role-input/output t)
(syntax-parse t
[(~Branch (~Effs τ-r ...) ...)
#:with (τi τo τa) (analyze-roles #'(τ-r ... ...))
(values #'τi #'τo #'τa)]
[(~Stop name:id τ-r ...)
#:with (τi τo τa) (analyze-roles #'(τ-r ...))
(values #'τi #'τo #'τa)]
[(~Actor τc)
(values (mk-U*- '()) (mk-U*- '()) t)]
[(~Sends τ-m)
(values (mk-U*- '()) (type-eval #'(Message τ-m)) (mk-U*- '()))]
[(~Role (name:id)
(~or (~Shares τ-s)
(~Sends τ-m)
(~Reacts τ-if τ-then ...)) ...
(~and (~Role _ ...) sub-role) ...)
(define-values (is os ss)
(for/fold ([ins '()]
[outs '()]
[spawns '()])
([t (in-syntax #'(τ-then ... ... sub-role ...))])
(define-values (i o s) (analyze-role-input/output t))
(values (cons i ins) (cons o outs) (cons s spawns))))
(define pat-types (stx-map event-desc-type #'(τ-if ...)))
(values (type-eval #`(U #,@is #,@pat-types))
(type-eval #`(U τ-s ... (Message τ-m) ... #,@os #,@(stx-map pattern-sub-type pat-types)))
(type-eval #`(U #,@ss)))]))
;; EventDescriptorType -> Type
(define-for-syntax (event-desc-type desc)
(syntax-parse desc
[(~Know τ) #'τ]
[(~¬Know τ) #'τ]
[(~Message τ) desc]
[_ (type-eval #'(U*))]))
;; PatternType -> Type
(define-for-syntax (pattern-sub-type pt)
(syntax-parse pt
[(~Message τ)
(define t (replace-bind-and-discard-with-★ #'τ))
(type-eval #`(Observe #,t))]
[τ
(define t (replace-bind-and-discard-with-★ #'τ))
(type-eval #`(Observe #,t))]))
;; TODO : can potentially use something like `subst` for this
(define-for-syntax (replace-bind-and-discard-with-★ t)
(syntax-parse t
[(~Bind _)
(type-eval #'★/t)]
[~Discard
(type-eval #'★/t)]
[(~U* τ ...)
(type-eval #`(U #,@(stx-map replace-bind-and-discard-with-★ #'(τ ...))))]
[(~Any/bvs τ-cons () τ ...)
#:when (reassemblable? t)
(define subitems (for/list ([t (in-syntax #'(τ ...))])
(replace-bind-and-discard-with-★ t)))
(reassemble-type t subitems)]
[_ t]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Subtyping and Judgments on Types
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(begin-for-syntax
(define-syntax ~Base
(pattern-expander
(syntax-parser
[(_ nm:id)
#'((~literal #%plain-app) nm)])))
;; Type Type -> Bool
;; subtyping
(define (<: t1 t2)
(syntax-parse #`(#,t1 #,t2)
[(_ ~★/t)
(flat-type? t1)]
[((~U* τ1 ...) _)
(stx-andmap (lambda (t) (<: t t2)) #'(τ1 ...))]
[(_ (~U* τ2:type ...))
(stx-ormap (lambda (t) (<: t1 t)) #'(τ2 ...))]
[((~Actor τ1) (~Actor τ2))
(and (<: #'τ1 #'τ2)
(<: ( (strip-? #'τ1) #'τ2) #'τ1))]
[((~→ τ-in1 ... τ-out1) (~→ τ-in2 ... τ-out2))
#:when (stx-length=? #'(τ-in1 ...) #'(τ-in2 ...))
(and (stx-andmap <: #'(τ-in2 ...) #'(τ-in1 ...))
(<: #'τ-out1 #'τ-out2))]
[(~Discard _)
#t]
[((~Base τ1:id) (~Base τ2:id))
(free-identifier=? #'τ1 #'τ2)]
[((~Any/bvs τ-cons1 () τ1 ...) (~Any/bvs τ-cons2 () τ2 ...))
#:when (free-identifier=? #'τ-cons1 #'τ-cons2)
#:do [(define variances (syntax-property #'τ-cons1 'arg-variances))]
#:when variances
#:when (stx-length=? #'(τ1 ...) #'(τ2 ...))
(for/and ([ty1 (in-syntax #'(τ1 ...))]
[ty2 (in-syntax #'(τ2 ...))]
[var (in-list variances)])
(match var
[(== covariant)
(<: ty1 ty2)]
[(== contravariant)
(<: ty2 ty1)]
[(== invariant)
(and (<: ty1 ty2)
(<: ty2 ty1))]
[(== irrelevant)
#t]))]
;; TODO: clauses for Roles, and so on
[_ #f]))
;; shortcuts for mapping
(define ((<:l l) r)
(<: l r))
(define ((<:r r) l)
(<: l r)))
;; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
;; MODIFYING GLOBAL TYPECHECKING STATE!!!!!
;; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
(begin-for-syntax
(current-typecheck-relation <:))
;; Flat-Type Flat-Type -> Type
(define-for-syntax ( t1 t2)
(unless (and (flat-type? t1) (flat-type? t2))
(error ' "expected two flat-types"))
(syntax-parse #`(#,t1 #,t2)
[(_ ~★/t)
t1]
[(~★/t _)
t2]
[((~U* τ1:type ...) _)
(type-eval #`(U #,@(stx-map (lambda (t) ( t t2)) #'(τ1 ...))))]
[(_ (~U* τ2:type ...))
(type-eval #`(U #,@(stx-map (lambda (t) ( t1 t)) #'(τ2 ...))))]
[((~AssertionSet τ1) (~AssertionSet τ2))
#:with τ12 ( #'τ1 #'τ2)
(type-eval #'(AssertionSet τ12))]
;; Also, using <: is OK, even though <: refers to ∩, because <:'s use of ∩ is only
;; in the Actor case.
[((~Base τ1:id) (~Base τ2:id))
#:when (free-identifier=? #'τ1 #'τ2)
t1]
[((~Any/bvs τ-cons1 () τ1 ...) (~Any/bvs τ-cons2 () τ2 ...))
#:when (free-identifier=? #'τ-cons1 #'τ-cons2)
#:when (stx-length=? #'(τ1 ...) #'(τ2 ...))
#:do [(define desc (syntax-property t1 isect-desc-key))]
#:when desc
(define slots (stx-map #'(τ1 ...) #'(τ2 ...)))
(match desc
[(== BASE)
(error "this isn't right")]
[(== CONTAINER-LIKE)
(reassemble-type t1 slots)]
[(== PRODUCT-LIKE)
(if (ormap bot? slots)
(type-eval #'(U))
(reassemble-type t1 slots))])]
[_ (type-eval #'(U))]))
;; Type Type -> Bool
;; first type is the contents of the set/dataspace
;; second type is the type of a pattern
(define-for-syntax (project-safe? t1 t2)
(define (project-safe* t1 t2)
(syntax-parse #`(#,t1 #,t2)
[(_ (~Bind τ2:type))
(and (finite? t1) (<: t1 #'τ2))]
[(_ ~Discard)
#t]
[(_ ~★/t)
#t]
[((~U* τ1:type ...) _)
(stx-andmap (lambda (t) (project-safe? t t2)) #'(τ1 ...))]
[(_ (~U* τ2:type ...))
(stx-andmap (lambda (t) (project-safe? t1 t)) #'(τ2 ...))]
[((~Any/bvs τ-cons1 () τ1 ...) (~Any/bvs τ-cons2 () τ2 ...))
#:when (free-identifier=? #'τ-cons1 #'τ-cons2)
#:when (stx-length=? #'(τ1 ...) #'(τ2 ...))
#:do [(define desc (syntax-property t1 isect-desc-key))]
#:when (equal? desc PRODUCT-LIKE)
(stx-andmap project-safe? #'(τ1 ...) #'(τ2 ...))]
[_ #t]))
(if (overlap? t1 t2)
(project-safe* t1 t2)
#t))
;; AssertionType PatternType -> Bool
;; Is it possible for things of these two types to match each other?
;; Flattish-Type = Flat-Types + ★/t, Bind, Discard (assertion and pattern types)
(define-for-syntax (overlap? t1 t2)
(define t22 (replace-bind-and-discard-with-★ t2))
(not (<: ( t1 t22) (mk-U*- '()))))
;; Flattish-Type -> Bool
(define-for-syntax (finite? t)
(syntax-parse t
[~★/t #f]
[(~U* τ:type ...)
(stx-andmap finite? #'(τ ...))]
[(~Base _) #t]
[(~Any/bvs τ-cons () τ ...)
(stx-andmap finite? #'(τ ...))]))
;; PatternType -> Type
(define-for-syntax (pattern-matching-assertions t)
(syntax-parse t
[(~Bind τ)
#'τ]
[~Discard
(type-eval #'★/t)]
[(~U* τ ...)
(type-eval #`(U #,@(stx-map pattern-matching-assertions #'(τ ...))))]
[(~Any/bvs τ-cons () τ ...)
#:when (reassemblable? t)
(define subitems (for/list ([t (in-syntax #'(τ ...))])
(pattern-matching-assertions t)))
(reassemble-type t subitems)]
[_ t]))
;; it's ok for x to respond to strictly more events than y
(define-for-syntax (condition-covers? x y)
(or
;; covers Start,Stop,Dataflow
(type=? x y)
(syntax-parse #`(#,x #,y)
[((~Know τ1) (~Know τ2))
(<: (pattern-matching-assertions #'τ2)
(pattern-matching-assertions #'τ1))]
[((~¬Know τ1) (~¬Know τ2))
(<: (pattern-matching-assertions #'τ2)
(pattern-matching-assertions #'τ1))]
[((~Message τ1) (~Message τ2))
(<: (pattern-matching-assertions #'τ2)
(pattern-matching-assertions #'τ1))]
[_ #f])))
;; RoleType RoleType -> Bool
;; Check that role r implements role spec (possibly does more)
(define-for-syntax (role-implements? r spec)
(syntax-parse #`(#,r #,spec)
;; TODO: cases for unions, stop
[((~Role (x:id) (~or (~Shares τ-s1) (~Sends τ-m1) (~Reacts τ-if1 τ-then1 ...)) ...)
(~Role (y:id) (~or (~Shares τ-s2) (~Sends τ-m2) (~Reacts τ-if2 τ-then2 ...)) ...))
#:when (free-identifier=? #'x #'y)
(and
;; for each assertion in the spec, there must be a suitable assertion in the actual
;; TODO: this kinda ignores numerosity, can one assertion in r cover multiple assertions in spec?
(for/and [(s2 (in-syntax #'(τ-s2 ...)))]
(stx-ormap (<:l s2) #'(τ-s1 ...)))
;; similar for messages
(for/and [(m2 (in-syntax #'(τ-m2 ...)))]
(stx-ormap (<:l m2) #'(τ-m1 ...)))
(for/and [(s2 (in-syntax #'((τ-if2 (τ-then2 ...)) ...)))]
(define/syntax-parse (τ-if2 (τ-then2 ...)) s2)
(for/or [(s1 (in-syntax #'((τ-if1 (τ-then1 ...)) ...)))]
(define/syntax-parse (τ-if1 (τ-then1 ...)) s1)
;; the event descriptors need to line up
(and (condition-covers? #'τ-if1 #'τ-if2)
;; and for each specified response to the event, there needs to be a similar one in the
;; the actual
(stx-andmap (lambda (s) (stx-ormap (lambda (r) (role-implements? r s)) #'(τ-then1 ...)))
#'(τ-then2 ...))))))]
[((~Role (x:id) _ ...)
(~Role (y:id) _ ...))
(role-implements? (subst #'y #'x r) spec)]
[((~Stop x:id τ1 ...)
(~Stop y:id τ2 ...))
(and
(free-identifier=? #'x #'y)
(for/and ([t2 (in-syntax #'(τ2 ...))])
(for/or ([t1 (in-syntax #'(τ1 ...))])
(role-implements? t1 t2))))]
;; seems like this check might be in the wrong place
[((~Sends τ-m1)
(~Sends τ-m2))
(<: #'τ-m1 #'τ-m2)]
[((~Actor _)
(~Actor _))
;; spawned actor OK in specified dataspace
(<: r spec)]))
(module+ test
(displayln "skipping commented for-syntax tests because it's slow")
#;(begin-for-syntax
;; TESTS
(let ()
;; utils
(local-require syntax/parse/define
rackunit)
(define te type-eval)
(define-syntax-parser check-role-implements?
[(_ r1 r2)
(quasisyntax/loc this-syntax
(check-true (role-implements? (te #'r1) (te #'r2))))])
(define-syntax-parser check-role-not-implements?
[(_ r1 r2)
(quasisyntax/loc this-syntax
(check-false (role-implements? (te #'r1) (te #'r2))))])
;; Name Related
(check-role-implements? (Role (x)) (Role (x)))
(check-role-implements? (Role (x)) (Role (y)))
;; Assertion Related
(check-role-not-implements? (Role (x)) (Role (y) (Shares Int)))
(check-role-implements? (Role (x) (Shares Int)) (Role (y)))
(check-role-implements? (Role (x) (Shares Int)) (Role (y) (Shares Int)))
(check-role-implements? (Role (x)
(Shares Int)
(Shares String))
(Role (y)
(Shares Int)
(Shares String)))
(check-role-implements? (Role (x)
(Shares String)
(Shares Int))
(Role (y)
(Shares Int)
(Shares String)))
(check-role-not-implements? (Role (x)
(Shares Int))
(Role (y)
(Shares Int)
(Shares String)))
;; Reactions
(check-role-implements? (Role (x)
(Reacts (Know Int)))
(Role (y)
(Reacts (Know Int))))
(check-role-implements? (Role (x)
(Reacts (Know Int))
(Shares String))
(Role (y)
(Reacts (Know Int))))
(check-role-implements? (Role (x)
(Reacts (Know Int)
(Role (y) (Shares String))))
(Role (y)
(Reacts (Know Int))))
(check-role-not-implements? (Role (x))
(Role (y)
(Reacts (Know Int))))
(check-role-not-implements? (Role (x)
(Reacts (Know String)))
(Role (y)
(Reacts (Know Int))))
;; these two might need to be reconsidered
(check-role-not-implements? (Role (x)
(Shares (Observe ★/t)))
(Role (y)
(Reacts (Know Int))))
(check-role-not-implements? (Role (x)
(Shares (Observe Int)))
(Role (y)
(Reacts (Know Int))))
(check-role-implements? (Role (x)
(Reacts (Know Int)
(Role (x2) (Shares String))))
(Role (y)
(Reacts (Know Int)
(Role (y2) (Shares String)))))
(check-role-implements? (Role (x)
(Reacts (¬Know Int)
(Role (x2) (Shares String))))
(Role (y)
(Reacts (¬Know Int)
(Role (y2) (Shares String)))))
(check-role-implements? (Role (x)
(Reacts OnStart
(Role (x2) (Shares String))))
(Role (y)
(Reacts OnStart
(Role (y2) (Shares String)))))
(check-role-implements? (Role (x)
(Reacts OnStop
(Role (x2) (Shares String))))
(Role (y)
(Reacts OnStop
(Role (y2) (Shares String)))))
(check-role-implements? (Role (x)
(Reacts OnDataflow
(Role (x2) (Shares String))))
(Role (y)
(Reacts OnDataflow
(Role (y2) (Shares String)))))
(check-role-not-implements? (Role (x)
(Reacts (Know Int)
(Role (x2) (Shares String))))
(Role (y)
(Reacts (Know Int)
(Role (y2) (Shares String))
(Role (y3) (Shares Int)))))
(check-role-implements? (Role (x)
(Reacts (Know Int)
(Role (x3) (Shares Int))
(Role (x2) (Shares String))))
(Role (y)
(Reacts (Know Int)
(Role (y2) (Shares String))
(Role (y3) (Shares Int)))))
;; also not sure about this one
(check-role-implements? (Role (x)
(Reacts (Know Int)
(Role (x2)
(Shares String)
(Shares Int))))
(Role (y)
(Reacts (Know Int)
(Role (y2) (Shares String))
(Role (y3) (Shares Int)))))
;; Stop
;; these all error when trying to create the Stop type :<
#|
(check-role-implements? (Role (x)
(Reacts OnStart (Stop x)))
(Role (x)
(Reacts OnStart (Stop x))))
(check-role-implements? (Role (x)
(Reacts OnStart (Stop x)))
(Role (y)
(Reacts OnStart (Stop y))))
(check-role-implements? (Role (x)
(Reacts OnStart (Stop x (Role (x2) (Shares Int)))))
(Role (y)
(Reacts OnStart (Stop y) (Role (y2) (Shares Int)))))
(check-role-not-implements? (Role (x)
(Reacts OnStart (Stop x (Role (x2) (Shares String)))))
(Role (y)
(Reacts OnStart (Stop y) (Role (y2) (Shares Int)))))
(check-role-not-implements? (Role (x)
(Reacts OnStart))
(Role (y)
(Reacts OnStart (Stop y) (Role (y2) (Shares Int)))))
|#
;; Spawning Actors
(check-role-implements? (Role (x)
(Reacts OnStart (Actor Int)))
(Role (x)
(Reacts OnStart (Actor Int))))
(check-role-implements? (Role (x)
(Reacts OnStart (Actor Int)))
(Role (x)
(Reacts OnStart (Actor (U Int String)))))
(check-role-not-implements? (Role (x)
(Reacts OnStart (Actor Bool)))
(Role (x)
(Reacts OnStart (Actor (U Int String)))))
)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Effect Checking
;; DesugaredSyntax EffectName -> (Syntaxof Effect ...)
(define-for-syntax (get-effect e- eff)
(or (syntax-property e- eff) #'()))
;; DesugaredSyntax EffectName -> Bool
(define-for-syntax (effect-free? e- eff)
(define prop (syntax-property e- eff))
(or (false? prop) (stx-null? prop)))
;; DesugaredSyntax -> Bool
(define-for-syntax (pure? e-)
(for/and ([key (in-list '(ν-ep ν-f ν-s))])
(effect-free? e- key)))
;; (SyntaxOf DesugaredSyntax ...) -> Bool
(define-for-syntax (all-pure? es)
(stx-andmap pure? es))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Core forms
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-for-syntax (int-def-ctx-bind-type-rename x x- t ctx)
(when DEBUG-BINDINGS?
(printf "adding to context ~a\n" (syntax-debug-info x)))
(syntax-local-bind-syntaxes (list x-) #f ctx)
(syntax-local-bind-syntaxes (list x)
#`(make-rename-transformer
(add-orig (assign-type #'#,x- #'#,t #:wrap? #f) #'#,x))
ctx))
(define-for-syntax (add-bindings-to-ctx e- def-ctx)
(syntax-parse e-
#:literals (erased field/intermediate define/intermediate begin-)
[(erased (field/intermediate (x:id x-:id τ e-) ...))
(for ([orig-name (in-syntax #'(x ... ))]
[new-name (in-syntax #'(x- ...))]
[field-ty (in-syntax #'(τ ...))])
(int-def-ctx-bind-type-rename orig-name new-name field-ty def-ctx))]
[(erased (define/intermediate x:id x-:id τ e-))
(int-def-ctx-bind-type-rename #'x #'x- #'τ def-ctx)]
#;[(erased (begin- e ...))
(for ([e (in-syntax #'(e ...))])
(add-bindings-to-ctx e def-ctx))]
[_ (void)]))
(define-for-syntax (display-ctx-bindings ctx)
(printf "context:\n")
(for ([x (in-list (internal-definition-context-binding-identifiers ctx))])
(printf ">>~a\n" (syntax-debug-info x))))
;; -> (Values e-... (Listof Type) (Listof EndpointEffects) (Listof FacetEffects) (Listof SpawnEffects))
;; recognizes local binding forms
;; (field/intermediate [x e] ...
;; (define/intermediate x x- τ e)
(define-for-syntax (walk/bind e...
[def-ctx (syntax-local-make-definition-context)]
[unique (gensym 'walk/bind)])
(define-values (rev-e-... rev-τ... ep-effects facet-effects spawn-effects)
(let loop ([e... (syntax->list e...)]
[rev-e-... '()]
[rev-τ... '()]
[ep-effects '()]
[facet-effects '()]
[spawn-effects '()])
(match e...
['()
(values rev-e-... rev-τ... ep-effects facet-effects spawn-effects)]
[(cons e more)
(when (and DEBUG-BINDINGS?
(identifier? e))
(display-ctx-bindings def-ctx)
(printf "expanding ~a\n" (syntax-debug-info e)))
(define e- (local-expand e (list unique) (list #'erased #'begin) def-ctx))
(syntax-parse e-
#:literals (begin)
[(begin e ...)
(loop (append (syntax->list #'(e ...)) more)
rev-e-...
rev-τ...
ep-effects
facet-effects
spawn-effects)]
[_
(define τ (syntax-property e- ':))
(define-values (ep-effs f-effs s-effs)
(values (syntax->list (get-effect e- 'ν-ep))
(syntax->list (get-effect e- 'ν-f))
(syntax->list (get-effect e- 'ν-s))))
(add-bindings-to-ctx e- def-ctx)
(loop more
(cons e- rev-e-...)
(cons τ rev-τ...)
(append ep-effs ep-effects)
(append f-effs facet-effects)
(append s-effs spawn-effects))])])))
(values (reverse rev-e-...)
(reverse rev-τ...)
ep-effects
facet-effects
spawn-effects))
(define-typed-syntax (start-facet name:id ep ...+)
#:with name- (syntax-local-identifier-as-binding (syntax-local-introduce (generate-temporary #'name)))
#:with name+ (syntax-local-identifier-as-binding #'name)
#:with facet-name-ty (type-eval #'FacetName)
#:do [(define ctx (syntax-local-make-definition-context))
(define unique (gensym 'start-facet))
(define name-- (internal-definition-context-introduce ctx #'name- 'add))
(int-def-ctx-bind-type-rename #'name+ #'name- #'facet-name-ty ctx)
(define-values (ep-... τ... ep-effects facet-effects spawn-effects)
(walk/bind #'(ep ...) ctx unique))
(unless (and (stx-null? facet-effects) (stx-null? spawn-effects))
(type-error #:src #'(ep ...) #:msg "only endpoint effects allowed"))]
#:with ((~or (~and τ-a (~Shares _))
;; untyped syndicate might allow this - TODO
#;(~and τ-m (~Sends _))
(~and τ-r (~Reacts _ ...))
~MakesField)
...)
ep-effects
#:with τ (type-eval #`(Role (#,name--)
τ-a ...
;; τ-m ...
τ-r ...))
--------------------------------------------------------------
[ (syndicate:react (let- ([#,name-- (syndicate:current-facet-id)])
#,@ep-...))
( : ★/t)
( ν-f (τ))])
(define-typed-syntax (field [x:id τ-f:type e:expr] ...)
#:fail-unless (stx-andmap flat-type? #'(τ-f ...)) "keep your uppity data outta my fields"
[ e e- ( : τ-f)] ...
#:fail-unless (stx-andmap pure? #'(e- ...)) "field initializers not allowed to have effects"
#:with (x- ...) (generate-temporaries #'(x ...))
#:with (τ ...) (stx-map type-eval #'((Field τ-f.norm) ...))
#:with MF (type-eval #'MakesField)
----------------------------------------------------------------------
[ (field/intermediate [x x- τ e-] ...)
( : ★/t)
( ν-ep (MF))])
(define-syntax (field/intermediate stx)
(syntax-parse stx
[(_ [x:id x-:id τ e-] ...)
#'(syndicate:field [x- e-] ...)]))
(define-typed-syntax (assert e:expr)
[ e e- ( : τ)]
#:fail-unless (pure? #'e-) "expression not allowed to have effects"
#:with τs (type-eval #'(Shares τ))
-------------------------------------
[ (syndicate:assert e-) ( : ★/t)
( ν-ep (τs))])
(define-typed-syntax (send! e:expr)
[ e e- ( : τ)]
#:fail-unless (pure? #'e-) "expression not allowed to have effects"
#:with τm (type-eval #'(Sends τ))
--------------------------------------
[ (syndicate:send! e-) ( : ★/t)
( ν-f (τm))])
(define-typed-syntax (stop facet-name:id cont ...)
[ facet-name facet-name- ( : FacetName)]
[ (begin #f cont ...) cont- ( ν-ep (~effs)) ( ν-s (~effs)) ( ν-f (~effs τ-f ...))]
#:with τ (mk-Stop- #`(facet-name- τ-f ...))
---------------------------------------------------------------------------------
[ (syndicate:stop-facet facet-name- cont-) ( : ★/t)
( ν-f (τ))])
(begin-for-syntax
(define-syntax-class asserted/retracted/message
#:datum-literals (asserted retracted message)
(pattern (~or (~and asserted
(~bind [syndicate-kw #'syndicate:asserted]
[react-con #'Know]))
(~and retracted
(~bind [syndicate-kw #'syndicate:retracted]
[react-con #'¬Know]))
(~and message
(~bind [syndicate-kw #'syndicate:message]
[react-con #'Message]))))))
(define-typed-syntax on
[(on (~literal start) s ...)
[ (begin s ...) s- ( ν-ep (~effs))
( ν-f (~effs τ-f ...))
( ν-s (~effs τ-s ...))]
#:with τ-r (type-eval #'(Reacts OnStart τ-f ... τ-s ...))
-----------------------------------
[ (syndicate:on-start s-) ( : ★/t)
( ν-ep (τ-r))]]
[(on (~literal stop) s ...)
[ (begin s ...) s- ( ν-ep (~effs))
( ν-f (~effs τ-f ...))
( ν-s (~effs τ-s ...))]
#:with τ-r (type-eval #'(Reacts OnStop τ-f ... τ-s ...))
-----------------------------------
[ (syndicate:on-stop s-) ( : ★/t)
( ν-ep (τ-r))]]
[(on (a/r/m:asserted/retracted/message p) s ...)
[ p p-- ( : τp)]
#:fail-unless (pure? #'p--) "pattern not allowed to have effects"
#:with ([x:id τ:type] ...) (pat-bindings #'p)
[[x x- : τ] ... (begin s ...) s-
( ν-ep (~effs))
( ν-f (~effs τ-f ...))
( ν-s (~effs τ-s ...))]
#:with p- (substs #'(x- ...) #'(x ...) (compile-syndicate-pattern #'p))
#:with τ-r (type-eval #'(Reacts (a/r/m.react-con τp) τ-f ... τ-s ...))
-----------------------------------
[ (syndicate:on (a/r/m.syndicate-kw p-)
s-)
( : ★/t)
( ν-ep (τ-r))]])
(define-typed-syntax (begin/dataflow s ...+)
[ (begin s ...) s-
( : _)
( ν-ep (~effs))
( ν-f (~effs τ-f ...))
( ν-s (~effs τ-s ...))]
#:with τ-r (type-eval #'(Reacts OnDataflow τ-f ... τ-s ...))
--------------------------------------------------
[ (syndicate:begin/dataflow s-)
( : ★/t)
( ν-ep (τ-r))])
;; 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 τ] ... ...)]
[_
#'()]))
;; 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-syndicate-pattern pat)
(compile-pattern pat
#'list-
(lambda (id) #`($ #,id))
identity))
(define-for-syntax (compile-match-pattern pat)
(compile-pattern pat
#'list
identity
(lambda (exp) #`(==- #,exp))))
(define-typed-syntax (spawn τ-c:type s)
#:fail-unless (flat-type? #'τ-c.norm) "Communication type must be first-order"
;; TODO: check that each τ-f is a Role
[ s s- ( ν-ep (~effs)) ( ν-s (~effs)) ( ν-f (~effs τ-f ...))]
;; TODO: s shouldn't refer to facets or fields!
#:with (τ-i τ-o τ-a) (analyze-roles #'(τ-f ...))
#:fail-unless (<: #'τ-o #'τ-c.norm)
(format "Output ~a not valid in dataspace ~a" (type->str #'τ-o) (type->str #'τ-c.norm))
#:fail-unless (<: #'τ-a
(type-eval #'(Actor τ-c.norm)))
"Spawned actors not valid in dataspace"
#:fail-unless (project-safe? ( (strip-? #'τ-o) #'τ-c.norm)
#'τ-i)
"Not prepared to handle all inputs"
#:with τ-final (type-eval #'(Actor τ-c.norm))
--------------------------------------------------------------------------------------------
[ (syndicate:spawn (syndicate:on-start s-)) ( : ★/t)
( ν-s (τ-final))])
(define-typed-syntax (dataspace τ-c:type s ...)
#:fail-unless (flat-type? #'τ-c.norm) "Communication type must be first-order"
[ s s- ( ν-ep (~effs)) ( ν-s (~effs τ-s ...)) ( ν-f (~effs))] ...
#:with τ-actor (type-eval #'(Actor τ-c.norm))
#:fail-unless (stx-andmap (lambda (t) (<: t #'τ-actor)) #'(τ-s ... ...))
"Not all actors conform to communication type"
#:with τ-ds-i (strip-inbound #'τ-c.norm)
#:with τ-ds-o (strip-outbound #'τ-c.norm)
#:with τ-relay (relay-interests #'τ-c.norm)
-----------------------------------------------------------------------------------
[ (syndicate:dataspace s- ...) ( : ★/t)
( ν-s ((Actor (U τ-ds-i τ-ds-o τ-relay))))])
(define-typed-syntax (set! x:id e:expr)
[ e e- ( : τ)]
#:fail-unless (pure? #'e-) "expression not allowed to have effects"
[ x x- ( : (~Field τ-x:type))]
#:fail-unless (<: #'τ #'τ-x) "Ill-typed field write"
----------------------------------------------------
[ (x- e-) ( : ★/t)])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Derived Forms
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-typed-syntax (during p s ...)
#:with inst-p (instantiate-pattern #'p)
----------------------------------------
[ (on (asserted p)
(start-facet during-inner
(on (retracted inst-p)
(stop during-inner))
s ...))])
;; TODO - reconcile this with `compile-pattern`
(define-for-syntax (instantiate-pattern pat)
(let loop ([pat pat])
(syntax-parse pat
#:datum-literals (tuple discard bind)
[(tuple p ...)
#`(tuple #,@(stx-map loop #'(p ...)))]
[(k:kons1 p)
#`(k #,(loop #'p))]
[(bind x:id τ)
#'x]
;; not sure about this
[discard
#'discard]
[(~constructor-exp ctor p ...)
(define/with-syntax uctor (untyped-ctor #'ctor))
#`(ctor #,@(stx-map loop #'(p ...)))]
[_
pat])))
(define-typed-syntax (define/query-value x:id e0 p e)
[ e0 e0- ( : τ)]
#:fail-unless (pure? #'e0-) "expression must be pure"
----------------------------------------
[ (begin (field [x τ e0-])
(on (asserted p)
(set! x e))
(on (retracted p)
(set! x e0-)))])
;; TODO: #:on-add
(define-typed-syntax (define/query-set x:id p e)
#:with ([y τ] ...) (pat-bindings #'p)
;; e will be re-expanded :/
[[y y- : τ] ... e e- τ-e]
----------------------------------------
[ (begin (field [x (Set τ-e) (set)])
(on (asserted p)
(set! x (set-add (ref x) e)))
(on (retracted p)
(set! x (set-remove (ref x) e))))])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Expressions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-typed-syntax (ref x:id)
[ x x- (~Field τ)]
------------------------
[ (x-) ( : τ)])
(define-typed-syntax (lambda ([x:id (~optional (~datum :)) τ:type] ...) body ...+)
[[x x- : τ] ... (begin body ...) body- ( : τ-e)
( ν-ep (~effs τ-ep ...))
( ν-s (~effs τ-s ...))
( ν-f (~effs τ-f ...))]
----------------------------------------
[ (lambda- (x- ...) body-) ( : ( τ ... (Computation (Value τ-e)
(Endpoints τ-ep ...)
(Roles τ-f ...)
(Spawns τ-s ...))))])
(define-typed-syntax (typed-app e_fn e_arg ...)
[ e_fn e_fn- ( : (~→ τ_in ... (~Computation (~Value τ-out)
(~Endpoints τ-ep ...)
(~Roles τ-f ...)
(~Spawns τ-s ...))))]
#:fail-unless (pure? #'e_fn-) "expression not allowed to have effects"
#:fail-unless (stx-length=? #'[τ_in ...] #'[e_arg ...])
(num-args-fail-msg #'e_fn #'[τ_in ...] #'[e_arg ...])
[ e_arg e_arg- ( : τ_in)] ...
#:fail-unless (stx-andmap pure? #'(e_arg- ...)) "expressions not allowed to have effects"
------------------------------------------------------------------------
[ (#%app- e_fn- e_arg- ...) ( : τ-out)
( ν-ep (τ-ep ...))
( ν-s (τ-s ...))
( ν-f (τ-f ...))])
(define-typed-syntax (tuple e:expr ...)
[ e e- ( : τ)] ...
#:fail-unless (stx-andmap pure? #'(e- ...)) "expressions not allowed to have effects"
-----------------------
[ (list- 'tuple e- ...) ( : (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)
--------------------------------------------------------------
[ (tuple-select n e-) ( : τr)])
(define- (tuple-select n t)
(list-ref- t (add1 n)))
;; it would be nice to abstract over these three
(define-typed-syntax (observe e:expr)
[ e e- ( : τ)]
#:fail-unless (pure? #'e-) "expression not allowed to have effects"
---------------------------------------------------------------------------
[ (syndicate:observe e-) ( : (Observe τ))])
(define-typed-syntax (inbound e:expr)
[ e e- ( : τ)]
#:fail-unless (pure? #'e-) "expression not allowed to have effects"
---------------------------------------------------------------------------
[ (syndicate:inbound e-) ( : (Inbound τ))])
(define-typed-syntax (outbound e:expr)
[ e e- ( : τ)]
#:fail-unless (pure? #'e-) "expression not allowed to have effects"
---------------------------------------------------------------------------
[ (syndicate:outbound e-) ( : (Outbound τ))])
(define-typed-syntax (message e:expr)
[ e e- ( : τ)]
#:fail-unless (pure? #'e-) "expression must be pure"
------------------------------
[ (syndicate:message e-) ( : (Message τ))])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 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) ])
(define-syntax (define/intermediate stx)
(syntax-parse stx
[(_ x:id x-:id τ e)
;; including a syntax binding for x allows for module-top-level references
;; (where walk/bind won't replace further uses) and subsequent provides
#'(begin-
(define-syntax x
(make-variable-like-transformer (add-orig (assign-type #'x- #'τ #:wrap? #f) #'x)))
(define- x- e))]))
;; copied from ext-stlc
(define-typed-syntax define
[(_ x:id (~datum :) τ:type e:expr)
[ e e- τ.norm]
#:fail-unless (pure? #'e-) "expression must be pure"
#:with x- (generate-temporary #'x)
#:with x+ (syntax-local-identifier-as-binding #'x)
--------
[ (define/intermediate x+ x- τ.norm e-) ( : ★/t)]]
[(_ x:id e)
;This won't work with mutually recursive definitions
[ e e- τ]
#:fail-unless (pure? #'e-) "expression must be pure"
#:with x- (generate-temporary #'x)
#:with x+ (syntax-local-identifier-as-binding #'x)
--------
[ (define/intermediate x+ x- τ e-) ( : ★/t)]]
[(_ (f [x (~optional (~datum :)) ty:type] ...
(~or (~datum ) (~datum ->)) ty_out:type)
e ...+)
[ (lambda ([x : ty] ...) (begin e ...)) e- ( : (~and fun-ty
(~→ (~not (~Computation _ ...)) ...
(~Computation (~Value τ-v)
_ ...))))]
#:fail-unless (<: #'τ-v #'ty_out.norm)
(format "expected different return type\n got ~a\n expected ~a\n"
#'τ-v #'ty_out
#;(type->str #'τ-v)
#;(type->str #'ty_out))
#:with f- (add-orig (generate-temporary #'f) #'f)
--------
[ (define/intermediate f f- fun-ty e-) ( : ★/t)]]
[(_ (f [x (~optional (~datum :)) ty] ...)
e ...+)
----------------------------
[ (define (f [x ty] ... -> ★/t) e ...)]])
;; 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-)
( ν-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 τ (type-eval #'(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 (begin s ...) #f)])
(define-typed-syntax (unless e s ...+)
------------------------------------
[ (if e #f (begin s ...))])
(define-typed-syntax begin
[(_ e_unit ... e)
#:do [(define-values (e-... τ... ep-effs f-effs s-effs) (walk/bind #'(e_unit ... e)))]
#:with τ (last τ...)
--------
[ (begin- #,@e-...) ( : τ)
( ν-ep (#,@ep-effs))
( ν-f (#,@f-effs))
( ν-s (#,@s-effs))]])
;; 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-)
( ν-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-typed-syntax (match e [p s ...+] ...+)
[ e e- ( : τ-e)]
#:fail-unless (pure? #'e-) "expression must be pure"
#:with (([x τ] ...) ...) (stx-map pat-bindings #'(p ...))
[[x x- : τ] ... (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 p-- τ-p] ...
#:fail-unless (project-safe? #'τ-e (type-eval #'(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 ...)
#'((x- ...) ...)
#'((x ...) ...))
--------------------------------------------------------------
[ (match- e- [p- s-] ...
[_ (error "incomplete pattern match")])
( : (U τ-s ...))
( ν-ep (eps ... ...))
( ν-f #,(make-Branch #'((fs ...) ...)))
( ν-s (ss ... ...))])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Ground Dataspace
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; n.b. this is a blocking operation, so an actor that uses this internally
;; won't necessarily terminate.
(define-typed-syntax (run-ground-dataspace τ-c:type s ...)
[ (dataspace τ-c s ...) ((~literal erased) ((~literal syndicate:dataspace) s- ...)) ( : t)]
-----------------------------------------------------------------------------------
[ (syndicate:run-ground s- ...) ( : (AssertionSet τ-c))])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Primitives
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; hmmm
(define-primop + ( Int Int (Computation (Value Int) (Endpoints) (Roles) (Spawns))))
(define-primop - ( Int Int (Computation (Value Int) (Endpoints) (Roles) (Spawns))))
(define-primop * ( Int Int (Computation (Value Int) (Endpoints) (Roles) (Spawns))))
#;(define-primop and ( Bool Bool Bool))
(define-primop or ( Bool Bool (Computation (Value Bool) (Endpoints) (Roles) (Spawns))))
(define-primop not ( Bool (Computation (Value Bool) (Endpoints) (Roles) (Spawns))))
(define-primop < ( Int Int (Computation (Value Bool) (Endpoints) (Roles) (Spawns))))
(define-primop > ( Int Int (Computation (Value Bool) (Endpoints) (Roles) (Spawns))))
(define-primop <= ( Int Int (Computation (Value Bool) (Endpoints) (Roles) (Spawns))))
(define-primop >= ( Int Int (Computation (Value Bool) (Endpoints) (Roles) (Spawns))))
(define-primop = ( Int Int (Computation (Value Bool) (Endpoints) (Roles) (Spawns))))
(define-primop bytes->string/utf-8 ( ByteString (Computation (Value String) (Endpoints) (Roles) (Spawns))))
(define-primop string->bytes/utf-8 ( String (Computation (Value ByteString) (Endpoints) (Roles) (Spawns))))
(define-primop gensym ( Symbol (Computation (Value Symbol) (Endpoints) (Roles) (Spawns))))
(define-primop symbol->string ( Symbol (Computation (Value String) (Endpoints) (Roles) (Spawns))))
(define-primop string->symbol ( String (Computation (Value Symbol) (Endpoints) (Roles) (Spawns))))
(define-typed-syntax (/ e1 e2)
[ e1 e1- ( : Int)]
[ e2 e2- ( : Int)]
#:fail-unless (pure? #'e1-) "expression not allowed to have effects"
#:fail-unless (pure? #'e2-) "expression not allowed to have effects"
------------------------
[ (exact-truncate- (/- e1- e2-)) ( : Int)])
;; for some reason defining `and` as a prim op doesn't work
(define-typed-syntax (and e ...)
[ e e- ( : Bool)] ...
#:fail-unless (stx-andmap pure? #'(e- ...)) "expressions not allowed to have effects"
------------------------
[ (and- e- ...) ( : Bool)])
(define-typed-syntax (equal? e1:expr e2:expr)
[ e1 e1- ( : τ1)]
#:fail-unless (flat-type? #'τ1)
(format "equality only available on flat data; got ~a" (type->str #'τ1))
[ e2 e2- ( : τ1)]
#:fail-unless (pure? #'e1-) "expression not allowed to have effects"
#:fail-unless (pure? #'e2-) "expression not allowed to have effects"
---------------------------------------------------------------------------
[ (equal?- e1- e2-) ( : Bool)])
(define-typed-syntax (displayln e:expr)
[ e e- ( : τ)]
#:fail-unless (pure? #'e-) "expression not allowed to have effects"
---------------
[ (displayln- e-) ( : ★/t)])
(define-typed-syntax (printf e ...+)
[ e e- ( : τ)] ...
#:fail-unless (stx-andmap pure? #'(e- ...)) "expression not allowed to have effects"
---------------
[ (printf- e- ...) ( : ★/t)])
(define-typed-syntax (~a e ...)
[ e e- ( : τ)] ...
#:fail-unless (stx-andmap flat-type? #'(τ ...))
"expressions must be string-able"
--------------------------------------------------
[ (~a- e- ...) ( : String)])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Basic Values
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-typed-syntax #%datum
[(_ . n:integer)
----------------
[ (#%datum- . n) ( : Int)]]
[(_ . b:boolean)
----------------
[ (#%datum- . b) ( : Bool)]]
[(_ . s:string)
----------------
[ (#%datum- . s) ( : String)]])
(define-typed-syntax (typed-quote x:id)
-------------------------------
[ (quote- x) ( : Symbol)])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Utilities
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-typed-syntax (print-type e)
[ e e- ( : τ) ( ν-ep (~effs eps ...)) ( ν-f (~effs fs ...)) ( ν-s (~effs ss ...))]
#:do [(pretty-display (type->str #'τ))]
----------------------------------
[ e- ( : τ) ( ν-ep (eps ...)) ( ν-f (fs ...)) ( ν-s (ss ...))])
(define-typed-syntax (print-role e)
[ e e- ( : τ) ( ν-ep (~effs eps ...)) ( ν-f (~effs fs ...)) ( ν-s (~effs ss ...))]
#:do [(for ([r (in-syntax #'(fs ...))])
(pretty-display (type->str r)))]
----------------------------------
[ e- ( : τ) ( ν-ep (eps ...)) ( ν-f (fs ...)) ( ν-s (ss ...))])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Lists
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-typed-syntax (list e ...)
[ e e- τ] ...
#:fail-unless (all-pure? #'(e- ...)) "expressions must be pure"
-------------------
[ (list- e- ...) (List (U τ ...))])
(define-typed-syntax (cons e1 e2)
[ e1 e1- τ1]
#:fail-unless (pure? #'e1-) "expression must be pure"
[ e2 e2- (~List τ2)]
#:fail-unless (pure? #'e2-) "expression must be pure"
#:with τ-l (type-eval #'(List (U τ1 τ2)))
----------------------------------------
[ (cons- e1- e2-) τ-l])
(define-typed-syntax (for/fold [acc:id e-acc]
[x:id e-list]
e-body ...+)
[ e-list e-list- (~List τ-l)]
#:fail-unless (pure? #'e-list-) "expression must be pure"
[ e-acc e-acc- τ-a]
#:fail-unless (pure? #'e-acc-) "expression must be pure"
[[x x- : τ-l] [acc acc- : τ-a] (begin e-body ...) e-body- τ-b]
#:fail-unless (pure? #'e-body-) "body must be pure"
#:fail-unless (<: #'τ-b #'τ-a)
"loop body doesn't match accumulator"
-------------------------------------------------------
[ (for/fold- ([acc- e-acc-])
([x- (in-list- e-list-)])
e-body-)
τ-b])
(define-typed-syntax (for ([x:id e-list] ...)
e-body ...+)
[ e-list e-list- (~List τ-l)] ...
#:fail-unless (all-pure? #'(e-list- ...)) "expressions must be pure"
[[x x- : τ-l] ... (begin e-body ...) e-body- ( : τ-b)
( ν-ep (~effs eps ...))
( ν-f (~effs fs ...))
( ν-s (~effs ss ...))]
-------------------------------------------------------
[ (for- ([x- (in-list- e-list-)] ...)
e-body-) ( : ★/t)
( ν-ep (eps ...))
( ν-f (fs ...))
( ν-s (ss ...))])
(define-typed-syntax (empty? e)
[ e e- (~List _)]
#:fail-unless (pure? #'e-) "expression must be pure"
-----------------------
[ (empty?- e-) Bool])
(define-typed-syntax (first e)
[ e e- (~List τ)]
#:fail-unless (pure? #'e-) "expression must be pure"
-----------------------
[ (first- e-) τ])
(define-typed-syntax (rest e)
[ e e- (~List τ)]
#:fail-unless (pure? #'e-) "expression must be pure"
-----------------------
[ (rest- e-) (List τ)])
(define-typed-syntax (member? e l)
[ e e- τe]
#:fail-unless (pure? #'e-) "expression must be pure"
[ l l- (~List τl)]
#:fail-unless (pure? #'l-) "expression must be pure"
#:fail-unless (<: #'τe #'τl) "incompatible list"
----------------------------------------
[ (member?- e- l-) Bool])
(define- (member?- v l)
(and- (member- v l) #t))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Sets
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-typed-syntax (set e ...)
[ e e- τ] ...
#:fail-unless (all-pure? #'(e- ...)) "expressions must be pure"
---------------
[ (set- e- ...) (Set (U τ ...))])
(define-typed-syntax (set-count e)
[ e e- (~Set _)]
#:fail-unless (pure? #'e-) "expression must be pure"
----------------------
[ (set-count- e-) Int])
(define-typed-syntax (set-add st v)
[ st st- (~Set τs)]
#:fail-unless (pure? #'st-) "expression must be pure"
[ v v- τv]
#:fail-unless (pure? #'v-) "expression must be pure"
-------------------------
[ (set-add- st- v-) (Set (U τs τv))])
(define-typed-syntax (set-remove st v)
[ st st- (~Set τs)]
#:fail-unless (pure? #'st-) "expression must be pure"
[ v v- τs]
#:fail-unless (pure? #'v-) "expression must be pure"
-------------------------
[ (set-remove- st- v-) (Set τs)])
(define-typed-syntax (set-member? st v)
[ st st- (~Set τs)]
#:fail-unless (pure? #'st-) "expression must be pure"
[ v v- τv]
#:fail-unless (pure? #'v-) "expression must be pure"
#:fail-unless (<: #'τv #'τs)
"type mismatch"
-------------------------------------
[ (set-member?- st- v-) Bool])
(define-typed-syntax (set-union st0 st ...)
[ st0 st0- (~Set τ-st0)]
#:fail-unless (pure? #'st0-) "expression must be pure"
[ st st- (~Set τ-st)] ...
#:fail-unless (all-pure? #'(st- ...)) "expressions must be pure"
-------------------------------------
[ (set-union- st0- st- ...) (Set (U τ-st0 τ-st ...))])
(define-typed-syntax (set-intersect st0 st ...)
[ st0 st0- (~Set τ-st0)]
#:fail-unless (pure? #'st0-) "expression must be pure"
[ st st- (~Set τ-st)] ...
#:fail-unless (all-pure? #'(st- ...)) "expressions must be pure"
#:with τr ( #'τ-st0 (type-eval #'(U τ-st ...)))
-------------------------------------
[ (set-intersect- st0- st- ...) (Set τr)])
(define-typed-syntax (set-subtract st0 st ...)
[ st0 st0- (~Set τ-st0)]
#:fail-unless (pure? #'st0-) "expression must be pure"
[ st st- (~Set _)] ...
#:fail-unless (all-pure? #'(st- ...)) "expressions must be pure"
-------------------------------------
[ (set-subtract- st0- st- ...) (Set τ-st0)])
(define-typed-syntax (list->set l)
[ l l- (~List τ)]
#:fail-unless (pure? #'l-) "expression must be pure"
-----------------------
[ (list->set- l-) (Set τ)])
(define-typed-syntax (set->list s)
[ s s- (~Set τ)]
#:fail-unless (pure? #'s-) "expression must be pure"
-----------------------
[ (set->list- s-) (List τ)])
(module+ test
(check-type (set 1 2 3)
: (Set Int)
-> (set- 2 3 1))
(check-type (set 1 "hello" 3)
: (Set (U Int String))
-> (set- "hello" 3 1))
(check-type (set-count (set 1 "hello" 3))
: Int
-> 3)
(check-type (set-union (set 1 2 3) (set "hello" "world"))
: (Set (U Int String))
-> (set- 1 2 3 "hello" "world"))
(check-type (set-intersect (set 1 2 3) (set "hello" "world"))
: (Set )
-> (set-))
(check-type (set-intersect (set 1 "hello" 3) (set #t "world" #f "hello"))
: (Set String)
-> (set- "hello")))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Tests
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(module+ test
(check-type (spawn (U (Message (Tuple String Int))
(Observe (Tuple String ★/t)))
(start-facet echo
(on (message (tuple "ping" (bind x Int)))
(send! (tuple "pong" x)))))
: ★/t)
(typecheck-fail (spawn (U (Message (Tuple String Int))
(Message (Tuple String String))
(Observe (Tuple String ★/t)))
(start-facet echo
(on (message (tuple "ping" (bind x Int)))
(send! (tuple "pong" x)))))))
;; local definitions
#;(module+ test
;; these cause an error in rackunit-typechecking, don't know why :/
#;(check-type (let ()
(begin
(define id : Int 1234)
id))
: Int
-> 1234)
#;(check-type (let ()
(define (spawn-cell [initial-value : Int])
(define id 1234)
id)
(typed-app spawn-cell 42))
: Int
-> 1234)
(check-equal? (let ()
(define id : Int 1234)
id)
1234)
#;(check-equal? (let ()
(define (spawn-cell [initial-value : Int])
(define id 1234)
id)
(typed-app spawn-cell 42))
1234))