diff --git a/base.rkt b/base.rkt index bc86caa..8192c09 100644 --- a/base.rkt +++ b/base.rkt @@ -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 diff --git a/default-recommendations/analyzers/function-expression-analyzer.rkt b/default-recommendations/analyzers/function-expression-analyzer.rkt index e5d935f..6c26446 100644 --- a/default-recommendations/analyzers/function-expression-analyzer.rkt +++ b/default-recommendations/analyzers/function-expression-analyzer.rkt @@ -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) diff --git a/default-recommendations/analyzers/identifier-usage.rkt b/default-recommendations/analyzers/identifier-usage.rkt index 5d990dc..396d0ae 100644 --- a/default-recommendations/analyzers/identifier-usage.rkt +++ b/default-recommendations/analyzers/identifier-usage.rkt @@ -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) diff --git a/default-recommendations/analyzers/ignored-result-values.rkt b/default-recommendations/analyzers/ignored-result-values.rkt index c2944ec..56e26c1 100644 --- a/default-recommendations/analyzers/ignored-result-values.rkt +++ b/default-recommendations/analyzers/ignored-result-values.rkt @@ -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) diff --git a/default-recommendations/analyzers/variable-mutability.rkt b/default-recommendations/analyzers/variable-mutability.rkt index 1647ca9..35a71da 100644 --- a/default-recommendations/analyzers/variable-mutability.rkt +++ b/default-recommendations/analyzers/variable-mutability.rkt @@ -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) diff --git a/grimoire.scrbl b/grimoire.scrbl index d6ff83e..02bf46d 100644 --- a/grimoire.scrbl +++ b/grimoire.scrbl @@ -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")] diff --git a/grimoire/expansion-analyzers.scrbl b/grimoire/expansion-analyzers.scrbl new file mode 100644 index 0000000..9a7ae7e --- /dev/null +++ b/grimoire/expansion-analyzers.scrbl @@ -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. diff --git a/private/syntax-property-bundle.rkt b/grimoire/syntax-property-bundle.rkt similarity index 98% rename from private/syntax-property-bundle.rkt rename to grimoire/syntax-property-bundle.rkt index 6f01269..9c4b43e 100644 --- a/private/syntax-property-bundle.rkt +++ b/grimoire/syntax-property-bundle.rkt @@ -12,7 +12,7 @@ [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 @@ -20,7 +20,8 @@ [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?)])) @@ -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 @@ -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) diff --git a/grimoire/syntax-property-bundle.scrbl b/grimoire/syntax-property-bundle.scrbl new file mode 100644 index 0000000..2ae2cc1 --- /dev/null +++ b/grimoire/syntax-property-bundle.scrbl @@ -0,0 +1,162 @@ +#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 collection of +@tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{syntax properties} that has been +detached from any particular syntax object. Each property in a bundle is addressed by the +combination of a @tech{syntax path}, identifying the subform the property belongs to, and a property +key. A bundle contains at most one value for each path and key combination. + +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, translated +from one syntax object's paths to another's, and inspected without ever touching a syntax object. +Resyntax's @tech{expansion analyzers} work this way --- each analyzer examines a fully expanded program and +reports its findings as a syntax property bundle whose paths refer to subforms of the expanded +program. Resyntax then translates those paths back to the corresponding locations in the original +unexpanded program and grafts the translated bundle onto the unexpanded syntax object with +@racket[syntax-add-all-properties], making the analyzers' findings visible to +@tech{refactoring rules}. + +Although syntax objects themselves permit arbitrary property keys, property keys in a bundle are +required to be interned symbols. The interned-symbol-keyed portion of a syntax object's property +table is the only portion that Racket exposes via @racket[syntax-property-symbol-keys], so it is +the only portion that bundles can faithfully represent. + + +@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.} + + +@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 #false]) + 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 + or @racket[#false], a contract error is raised instead. Note that this means @racket[#false] + cannot be used directly as a failure result --- use @racket[(λ () #false)] to make a missing + property produce @racket[#false].} + + +@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 descendants of @racket[path] are not + included. Returns an empty hash 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. 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.} diff --git a/main.rkt b/main.rkt index 7cafb32..853b5f4 100644 --- a/main.rkt +++ b/main.rkt @@ -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) diff --git a/private/analysis.rkt b/private/analysis.rkt index 2b9af38..e723a5b 100644 --- a/private/analysis.rkt +++ b/private/analysis.rkt @@ -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) diff --git a/private/analyzer.rkt b/private/analyzer.rkt index 2d1041e..3dba14d 100644 --- a/private/analyzer.rkt +++ b/private/analyzer.rkt @@ -14,7 +14,7 @@ (require rebellion/custom-write - resyntax/private/syntax-property-bundle) + resyntax/grimoire/syntax-property-bundle) ;@---------------------------------------------------------------------------------------------------- diff --git a/test/private/rackunit.rkt b/test/private/rackunit.rkt index 1a8323a..5f37d69 100644 --- a/test/private/rackunit.rkt +++ b/test/private/rackunit.rkt @@ -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