Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion base.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@
(module+ test
(require rackunit
resyntax/private/analyzer
resyntax/private/syntax-property-bundle)
resyntax/grimoire/syntax-property-bundle)

(test-case "refactoring-rule stores analyzers"
(define-refactoring-rule test-rule
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
rebellion/streaming/transducer
resyntax/private/analyzer
resyntax/grimoire/syntax-path
resyntax/private/syntax-property-bundle
resyntax/grimoire/syntax-property-bundle
resyntax/private/syntax-traversal
syntax/parse)

Expand Down
2 changes: 1 addition & 1 deletion default-recommendations/analyzers/identifier-usage.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
resyntax/default-recommendations/analyzers/private/expanded-id-table
resyntax/private/analyzer
resyntax/grimoire/syntax-path
resyntax/private/syntax-property-bundle
resyntax/grimoire/syntax-property-bundle
resyntax/private/syntax-traversal
syntax/id-table
syntax/parse)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
(require racket/stream
resyntax/private/analyzer
resyntax/grimoire/syntax-path
resyntax/private/syntax-property-bundle
resyntax/grimoire/syntax-property-bundle
resyntax/private/syntax-traversal
syntax/parse)

Expand Down
2 changes: 1 addition & 1 deletion default-recommendations/analyzers/variable-mutability.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
resyntax/default-recommendations/analyzers/private/expanded-id-table
resyntax/private/analyzer
resyntax/grimoire/syntax-path
resyntax/private/syntax-property-bundle
resyntax/grimoire/syntax-property-bundle
resyntax/private/syntax-traversal
syntax/id-table
syntax/parse)
Expand Down
2 changes: 2 additions & 0 deletions grimoire.scrbl
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ programmatically on anything found here.
@include-section[(lib "resyntax/grimoire/source.scrbl")]
@include-section[(lib "resyntax/grimoire/source-group.scrbl")]
@include-section[(lib "resyntax/grimoire/syntax-path.scrbl")]
@include-section[(lib "resyntax/grimoire/syntax-property-bundle.scrbl")]
@include-section[(lib "resyntax/grimoire/expansion-analyzers.scrbl")]
@include-section[(lib "resyntax/grimoire/string-replacement.scrbl")]
12 changes: 12 additions & 0 deletions grimoire/expansion-analyzers.scrbl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#lang scribble/manual


@title[#:tag "expansion-analyzers"]{Expansion Analyzers}

An @deftech{expansion analyzer} examines the fully expanded form of a program and reports facts
about it as a @tech{syntax property bundle} whose @tech{syntax paths} refer to subforms of the
expanded program. Resyntax translates those paths back to the corresponding subforms of the
original unexpanded program and attaches the reported properties to them, making information that
is only discoverable after macro expansion available to @tech{refactoring rules}. Expansion
analyzers are an as-yet undocumented work in progress; a future edition of this grimoire will
describe them properly.
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@
[syntax-property-bundle-as-map (-> syntax-property-bundle? immutable-sorted-map?)]
[syntax-property-bundle-entries (-> syntax-property-bundle? (sequence/c syntax-property-entry?))]
[syntax-property-bundle-get-property
(->* (syntax-property-bundle? syntax-path? any/c) (failure-result/c) any/c)]
(->* (syntax-property-bundle? syntax-path? interned-symbol?) (failure-result/c) any/c)]
[syntax-property-bundle-get-immediate-properties
(-> syntax-property-bundle? syntax-path? immutable-hash?)]
[syntax-property-bundle-get-all-properties
(-> syntax-property-bundle? syntax-path? syntax-property-bundle?)]
[sequence->syntax-property-bundle (-> (sequence/c syntax-property-entry?) syntax-property-bundle?)]
[into-syntax-property-bundle (reducer/c syntax-property-entry? syntax-property-bundle?)]
[property-hashes-into-syntax-property-bundle
(reducer/c (entry/c syntax-path? immutable-hash?) syntax-property-bundle?)]
(reducer/c (entry/c syntax-path? (hash/c interned-symbol? any/c #:immutable #true #:flat? #true))
syntax-property-bundle?)]
[syntax-add-all-properties (-> syntax? syntax-property-bundle? syntax?)]
[syntax-immediate-properties (->* (syntax?) (#:base-path syntax-path?) syntax-property-bundle?)]
[syntax-all-properties (->* (syntax?) (#:base-path syntax-path?) syntax-property-bundle?)]))
Expand All @@ -32,6 +33,7 @@
racket/sequence
racket/stream
rebellion/base/range
rebellion/base/symbol
rebellion/collection/entry
(except-in rebellion/collection/hash mutable-hash? immutable-hash?)
rebellion/collection/sorted-map
Expand All @@ -55,7 +57,7 @@
#:transparent)

(struct syntax-property-entry (path key value)
#:guard (struct-guard/c syntax-path? any/c any/c)
#:guard (struct-guard/c syntax-path? interned-symbol? any/c)
#:transparent)


Expand Down Expand Up @@ -130,7 +132,10 @@
(check-equal? actual expected)))


(define (syntax-property-bundle-get-property prop-bundle path key [failure-result #false])
(define absent-property (gensym 'absent-property))


(define (syntax-property-bundle-get-property prop-bundle path key [failure-result absent-property])
(define props-at-path (sorted-map-get (syntax-property-bundle-as-map prop-bundle) path (hash)))

(define (fail)
Expand All @@ -141,7 +146,7 @@
"property key" key
"properties at path" props-at-path))

(hash-ref props-at-path key (or failure-result fail)))
(hash-ref props-at-path key (if (eq? failure-result absent-property) fail failure-result)))


(module+ test
Expand Down Expand Up @@ -175,7 +180,12 @@
(define path (syntax-path (list 1 2 3)))
(define actual
(syntax-property-bundle-get-property (syntax-property-bundle) path 'foo (λ () 42)))
(check-equal? actual 42))))
(check-equal? actual 42))

(test-case "empty bundle with false failure value provided"
(define path (syntax-path (list 1 2 3)))
(define actual (syntax-property-bundle-get-property (syntax-property-bundle) path 'foo #false))
(check-equal? actual #false))))


(define (syntax-property-bundle-get-immediate-properties prop-bundle path)
Expand Down Expand Up @@ -220,7 +230,32 @@

(module+ test
(test-case "syntax-property-bundle-get-all-properties"
(void)))
(define props
(syntax-property-bundle
(syntax-property-entry root-syntax-path 'root-prop 0)
(syntax-property-entry (syntax-path (list 1)) 'list-prop 1)
(syntax-property-entry (syntax-path (list 1 0)) 'leaf-prop 2)
(syntax-property-entry (syntax-path (list 2)) 'other-prop 3)))

(test-case "properties at and under path are re-rooted relative to it"
(define actual (syntax-property-bundle-get-all-properties props (syntax-path (list 1))))
(define expected
(syntax-property-bundle
(syntax-property-entry root-syntax-path 'list-prop 1)
(syntax-property-entry (syntax-path (list 0)) 'leaf-prop 2)))
(check-equal? actual expected))

(test-case "root path returns the bundle unchanged"
(check-equal? (syntax-property-bundle-get-all-properties props root-syntax-path) props))

(test-case "path with no properties at or under it returns the empty bundle"
(define actual (syntax-property-bundle-get-all-properties props (syntax-path (list 5))))
(check-equal? actual (syntax-property-bundle)))

(test-case "empty bundle"
(define actual
(syntax-property-bundle-get-all-properties (syntax-property-bundle) (syntax-path (list 1))))
(check-equal? actual (syntax-property-bundle)))))


(define (syntax-property-bundle-entries prop-bundle)
Expand Down
181 changes: 181 additions & 0 deletions grimoire/syntax-property-bundle.scrbl
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
#lang scribble/manual


@(require (for-label racket/base
racket/contract/base
racket/mutability
racket/sequence
rebellion/base/symbol
rebellion/collection/entry
rebellion/collection/sorted-map
rebellion/streaming/reducer
resyntax/grimoire/syntax-path
resyntax/grimoire/syntax-property-bundle))


@title[#:tag "syntax-property-bundle"]{Syntax Property Bundles}
@defmodule[resyntax/grimoire/syntax-property-bundle]

A @deftech{syntax property bundle} is an immutable tree of
@tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{syntax properties} that has been
detached from any particular syntax object tree. Each property in a bundle is addressed by the
combination of a @tech{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 @tech{expansion analyzers} work this
way --- each analyzer examines a fully expanded module and reports its findings as a syntax property
bundle. Resyntax then combines each analyzer's output bundle together, rearranges the combined bundle,
and grafts it onto the original, unexpanded syntax object before analyzing it with
@tech{refactoring rules}.

Although syntax objects themselves permit arbitrary property keys, property keys in a bundle are
more limited. A syntax property bundle key @emph{must} be an interned symbol. This is for two reasons,
one specific and one general:

@itemlist[
@item{Interned symbol keys are the only syntax property keys that can be iterated by
@racket[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.}

@item{More generally, @bold{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 @racket[gensym]s or similar unserializable unique values, and the Racket macro
system takes care to make sure no API exists that provides uncontrolled access to them.}]


@section{Constructing Syntax Property Bundles}


@defproc[(syntax-property-bundle? [v any/c]) boolean?]{
A predicate that recognizes @tech{syntax property bundles}.}


@defstruct*[syntax-property-entry ([path syntax-path?] [key interned-symbol?] [value any/c])
#:transparent]{
A @deftech{syntax property entry} pairs a syntax property key and value with the
@tech{syntax path} of the subform that the property belongs to. Bundles are built out of these
entries.}


@defproc[(syntax-property-bundle [entry syntax-property-entry?] ...) syntax-property-bundle?]{
Constructs a @tech{syntax property bundle} containing each @racket[entry]. The entries may be given
in any order, but no two entries may share both a path and a key --- a contract error is raised if
they do.}


@defproc[(sequence->syntax-property-bundle [entries (sequence/c syntax-property-entry?)])
syntax-property-bundle?]{
Like @racket[syntax-property-bundle], but the entries are supplied as a sequence instead of as
individual arguments.}


@defthing[into-syntax-property-bundle (reducer/c syntax-property-entry? syntax-property-bundle?)]{
A @tech[#:doc '(lib "rebellion/main.scrbl")]{reducer} that collects a sequence of
@tech{syntax property entries} into a @tech{syntax property bundle}. Like
@racket[syntax-property-bundle], no two reduced entries may share both a path and a key.}


@defthing[property-hashes-into-syntax-property-bundle
(reducer/c (entry/c syntax-path? (hash/c interned-symbol? any/c #:immutable #true))
syntax-property-bundle?)]{
A @tech[#:doc '(lib "rebellion/main.scrbl")]{reducer} that collects a sequence of
@racket[entry] values, each mapping a @tech{syntax path} to a hash of the properties at that path,
into a @tech{syntax property bundle}. Entries with empty property
hashes are ignored. Each path may occur at most once in the reduced sequence --- unlike
@racket[into-syntax-property-bundle], this reducer does not merge multiple entries that refer to
the same path, and instead raises a contract error. This is because this reducer is intended to be
used when a syntax object and its subforms are iterated, and each syntax object's properties are all
extracted at once using @racket[syntax-property-symbol-keys]. Such a client would expect to never
iterate two syntax objects with the same syntax path, even if they had disjoint property keys.}


@section{Querying Syntax Property Bundles}


@defproc[(syntax-property-bundle-get-property
[bundle syntax-property-bundle?]
[path syntax-path?]
[key interned-symbol?]
[failure-result failure-result/c
(λ () (raise (make-exn:fail:contract ....)))])
any/c]{
Returns the value of the property with key @racket[key] at @racket[path] within @racket[bundle].
If no such property exists, then @racket[failure-result] determines the result: if it's a
procedure, it's called with no arguments to produce the result, and otherwise it's returned
directly, following the same protocol as @racket[hash-ref]. If @racket[failure-result] is omitted,
a contract error is raised instead.}


@defproc[(syntax-property-bundle-get-immediate-properties [bundle syntax-property-bundle?]
[path syntax-path?])
immutable-hash?]{
Returns a hash of every property in @racket[bundle] located at exactly @racket[path], mapping
property keys to property values. Properties located at @bold{descendants} of @racket[path] are
@bold{not} included. Returns an empty hash, rather than raising an error, if @racket[bundle]
contains no properties at @racket[path].}


@defproc[(syntax-property-bundle-get-all-properties [bundle syntax-property-bundle?]
[path syntax-path?])
syntax-property-bundle?]{
Returns a @tech{syntax property bundle} of every property in @racket[bundle] located at
@racket[path] or at any descendant of @racket[path]. The paths of the returned bundle are made
relative to @racket[path], as though by @racket[syntax-path-remove-prefix]: properties located at
exactly @racket[path] appear at @racket[root-syntax-path] in the returned bundle. Returns the
empty bundle, rather than raising an error, if @racket[bundle] contains no properties at or under
@racket[path]. Passing @racket[root-syntax-path] returns @racket[bundle] unchanged.}


@defproc[(syntax-property-bundle-entries [bundle syntax-property-bundle?])
(sequence/c syntax-property-entry?)]{
Returns a lazy sequence of every @tech{syntax property entry} in @racket[bundle]. Entries are
produced in ascending order of their paths, in the sense of @racket[syntax-path<=>]. The relative
order of multiple entries at the same path is unspecified.}


@defproc[(syntax-property-bundle-as-map [bundle syntax-property-bundle?]) immutable-sorted-map?]{
Returns a view of @racket[bundle] as a sorted map whose keys are @tech{syntax paths}, ordered by
@racket[syntax-path<=>], and whose values are hashes mapping property keys to property values.
Only paths with at least one property appear in the map.}


@section{Moving Properties Between Bundles and Syntax Objects}


@defproc[(syntax-immediate-properties [stx syntax?]
[#:base-path base-path syntax-path? root-syntax-path])
syntax-property-bundle?]{
Returns a @tech{syntax property bundle} of the properties attached directly to @racket[stx],
ignoring any properties attached to its subforms. Only properties whose keys are interned symbols
are extracted, as determined by @racket[syntax-property-symbol-keys]. The extracted properties are
located at @racket[base-path] in the returned bundle, which is useful when @racket[stx] is itself
a subform of some larger syntax object.}


@defproc[(syntax-all-properties [stx syntax?]
[#:base-path base-path syntax-path? root-syntax-path])
syntax-property-bundle?]{
Returns a @tech{syntax property bundle} of the properties attached to @racket[stx] and to every
subform within it. Only properties whose keys are interned symbols are extracted, as determined by
@racket[syntax-property-symbol-keys]. Each extracted property is located at the @tech{syntax path}
of the subform it was attached to, prefixed with @racket[base-path]. Subforms of hash datums are
not traversed, as syntax paths cannot refer to them --- see @secref["original-syntax-paths"] for
further explanation.}


@defproc[(syntax-add-all-properties [stx syntax?] [bundle syntax-property-bundle?]) syntax?]{
Returns a copy of @racket[stx] in which, for each @tech{syntax property entry} in @racket[bundle],
the subform at the entry's path is given the entry's property key and value as though by
@racket[syntax-property]. Properties already attached to @racket[stx] and its subforms are
retained, except where an entry in @racket[bundle] overwrites them. Every path in @racket[bundle]
must refer to a subform of @racket[stx], in the sense of @racket[syntax-contains-path?] ---
otherwise a contract error is raised.}
2 changes: 1 addition & 1 deletion main.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
resyntax/grimoire/source
resyntax/private/string-indent
resyntax/grimoire/string-replacement
resyntax/private/syntax-property-bundle
resyntax/grimoire/syntax-property-bundle
resyntax/private/syntax-range
resyntax/private/syntax-replacement
(except-in racket/list range)
Expand Down
2 changes: 1 addition & 1 deletion private/analysis.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
resyntax/private/syntax-movement
resyntax/private/syntax-neighbors
resyntax/grimoire/syntax-path
resyntax/private/syntax-property-bundle
resyntax/grimoire/syntax-property-bundle
resyntax/private/syntax-traversal
syntax/parse)

Expand Down
2 changes: 1 addition & 1 deletion private/analyzer.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@


(require rebellion/custom-write
resyntax/private/syntax-property-bundle)
resyntax/grimoire/syntax-property-bundle)


;@----------------------------------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion test/private/rackunit.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
resyntax/private/string-indent
resyntax/grimoire/string-replacement
resyntax/grimoire/syntax-path
resyntax/private/syntax-property-bundle
resyntax/grimoire/syntax-property-bundle
resyntax/private/syntax-traversal
syntax/modread
syntax/parse
Expand Down
Loading