4.4 Syntax Property Bundles
| (require resyntax/grimoire/syntax-property-bundle) | |
| package: resyntax | |
A syntax property bundle is an immutable tree of syntax properties that has been detached from any particular syntax object tree. Each property in a bundle is addressed by the combination of a syntax path, identifying the subform the property belongs to, and a symbol property key. A bundle contains at most one value for each path and key combination. Property bundles are a plain data representation of metadata that can be attached to and extracted from arbitrary syntax objects and their subforms.
Ordinarily, syntax properties live directly on syntax objects. Representing them separately as plain
data lets Resyntax manipulate the properties themselves: bundles can be filtered, merged, inspected,
and rearranged without ever touching a syntax object. Resyntax’s expansion analyzers work this
way —
Although syntax objects themselves permit arbitrary property keys, property keys in a bundle are more limited. A syntax property bundle key must be an interned symbol. This is for two reasons, one specific and one general:
Interned symbol keys are the only syntax property keys that can be iterated by syntax-property-symbol-keys, so such keys are the only kind that bundles can faithfully transfer between syntax objects. Since the entire purpose of property bundles is to transfer properties between syntax objects, allowing uninterned symbol keys would have no purpose.
More generally, syntax objects are not plain serializable data. Syntax objects are not safe to transfer from one Racket process to another. This is relied upon by various layers of the Racket macro system and similar low-level systems for encapsulation purposes. Some syntax properties are only used during macro expansion with opaque keys to provide private communication channels between macros that implement some library abstraction. Exposing such keys directly would allow intermediate macros in client code using that library to violate its abstractions. These sorts of opaque keys are often gensyms or similar unserializable unique values, and the Racket macro system takes care to make sure no API exists that provides uncontrolled access to them.
4.4.1 Constructing Syntax Property Bundles
procedure
v : any/c
struct
(struct syntax-property-entry (path key value) #:transparent) path : syntax-path? key : interned-symbol? value : any/c
procedure
(syntax-property-bundle entry ...) → syntax-property-bundle?
entry : syntax-property-entry?
> (syntax-property-bundle (syntax-property-entry root-syntax-path 'color 'red) (syntax-property-entry (syntax-path (list 2 0)) 'shape 'circle))
(syntax-property-bundle
(immutable-sorted-map
#<syntax-path:/> '#hash((color . red))
#<syntax-path:/2/0> '#hash((shape . circle))))
; Two entries at the same path and key are not allowed.
> (syntax-property-bundle (syntax-property-entry root-syntax-path 'color 'red) (syntax-property-entry root-syntax-path 'color 'blue)) syntax-property-bundle: multiple values for the same
property key at the same path
path: #<syntax-path:/>
property key: 'color
value: 'red
other value: 'blue
procedure
(sequence->syntax-property-bundle entries)
→ syntax-property-bundle? entries : (sequence/c syntax-property-entry?)
value
:
(reducer/c (entry/c syntax-path? (hash/c interned-symbol? any/c #:immutable #true)) syntax-property-bundle?)
4.4.2 Querying Syntax Property Bundles
procedure
(syntax-property-bundle-get-property bundle path key [ failure-result]) → any/c bundle : syntax-property-bundle? path : syntax-path? key : interned-symbol?
failure-result : failure-result/c = (λ () (raise (make-exn:fail:contract ....)))
(define bundle (syntax-property-bundle (syntax-property-entry root-syntax-path 'color 'red) (syntax-property-entry (syntax-path (list 2 0)) 'shape 'circle)))
> (syntax-property-bundle-get-property bundle root-syntax-path 'color) 'red
> (syntax-property-bundle-get-property bundle root-syntax-path 'size 'unknown) 'unknown
> (syntax-property-bundle-get-property bundle root-syntax-path 'size) syntax-property-bundle-get-property: no property value for
given key at given path
path: #<syntax-path:/>
property key: 'size
properties at path: '#hash((color . red))
procedure
(syntax-property-bundle-get-immediate-properties bundle path) → immutable-hash? bundle : syntax-property-bundle? path : syntax-path?
(define bundle (syntax-property-bundle (syntax-property-entry root-syntax-path 'color 'red) (syntax-property-entry (syntax-path (list 2 0)) 'shape 'circle)))
> (syntax-property-bundle-get-immediate-properties bundle (syntax-path (list 2 0))) '#hash((shape . circle))
; Descendant properties are excluded, and a path with none gives an empty hash. > (syntax-property-bundle-get-immediate-properties bundle (syntax-path (list 2))) '#hash()
procedure
(syntax-property-bundle-get-all-properties bundle path) → syntax-property-bundle? bundle : syntax-property-bundle? path : syntax-path?
(define bundle (syntax-property-bundle (syntax-property-entry (syntax-path (list 2)) 'a 1) (syntax-property-entry (syntax-path (list 2 0)) 'b 2)))
; Paths are made relative to /2: the /2 property lands at the root, /2/0 at /0.
> (sequence->list (syntax-property-bundle-entries (syntax-property-bundle-get-all-properties bundle (syntax-path (list 2)))))
(list
(syntax-property-entry #<syntax-path:/> 'a 1)
(syntax-property-entry #<syntax-path:/0> 'b 2))
procedure
(syntax-property-bundle-entries bundle)
→ (sequence/c syntax-property-entry?) bundle : syntax-property-bundle?
; Entries come out in ascending path order, regardless of construction order.
> (sequence->list (syntax-property-bundle-entries (syntax-property-bundle (syntax-property-entry (syntax-path (list 2 0)) 'shape 'circle) (syntax-property-entry root-syntax-path 'color 'red))))
(list
(syntax-property-entry #<syntax-path:/> 'color 'red)
(syntax-property-entry #<syntax-path:/2/0> 'shape 'circle))
procedure
(syntax-property-bundle-as-map bundle) → immutable-sorted-map?
bundle : syntax-property-bundle?
4.4.3 Moving Properties Between Bundles and Syntax Objects
procedure
(syntax-immediate-properties stx [ #:base-path base-path]) → syntax-property-bundle? stx : syntax? base-path : syntax-path? = root-syntax-path
procedure
(syntax-all-properties stx [ #:base-path base-path]) → syntax-property-bundle? stx : syntax? base-path : syntax-path? = root-syntax-path
(define stx (datum->syntax #false (list (syntax-property #'inner 'note 'here) #'outer)))
; Every interned-symbol property, from stx and its subforms, is collected. > (sequence->list (syntax-property-bundle-entries (syntax-all-properties stx))) (list (syntax-property-entry #<syntax-path:/0> 'note 'here))
procedure
(syntax-add-all-properties stx bundle) → syntax?
stx : syntax? bundle : syntax-property-bundle?
(define stx #'(a b c)) (define bundle (syntax-property-bundle (syntax-property-entry (syntax-path (list 0)) 'note 'here)))
; The property is grafted onto the subform at path /0, which is #'a. > (syntax-property (car (syntax->list (syntax-add-all-properties stx bundle))) 'note) 'here