4.6 String Replacements
| (require resyntax/grimoire/string-replacement) | |
| package: resyntax | |
A string replacement is a value describing an edit to a string. A replacement identifies a
region of the string to replace —
Inserted strings, constructed with inserted-string, containing brand new text to insert.
Copied strings, constructed with copied-string, containing a range of positions to copy from the original string. Copied pieces may copy from anywhere in the original string, not just from within the replaced region, so a replacement can move text around in addition to inserting and deleting it.
All positions are zero-based character offsets, and regions are half-open: a replacement from position start to position end covers the characters at positions start through (sub1 end). Beware that string replacements do NOT use one-based position indices, unlike positions in syntax object source locations such as those returned by syntax-position.
When applied to a string, a string replacement first deletes everything in the range it describes. Then it inserts and copies text into that range, according to the string replacement’s piece list. The replacement is applied atomically: character positions in copied strings always refer to the positions of characters in the original, unedited string, prior to any deletions, insertions, or copying operations being applied.
String replacements are the final, lowest-level form that Resyntax’s refactoring suggestions take before being written to files. The distinction between inserted and copied pieces is the string-level end of the formatting preservation mechanism described in Original Syntax Paths and Formatting Preservation: when Resyntax decides that a piece of refactored code is unchanged from the original program, the suggestion copies its text rather than regenerating it, and that decision ultimately takes the form of a copied-string piece. This mechanism is also used by Resyntax to preserve comments where possible and discard suggestions that can’t preserve them; see string-replacement-preserved-locations for details.
procedure
(string-replacement? v) → boolean?
v : any/c
procedure
(string-replacement #:start start #:end end #:contents contents) → string-replacement? start : natural? end : natural? contents : (sequence/c string-piece?)
The contents are lightly normalized during construction: pieces that span zero characters are dropped, adjacent inserted strings are merged into one, and adjacent copied strings that copy contiguous regions are merged into one. As a consequence, two replacements constructed from differently divided piece lists describing the same content are equal?, and string-replacement-contents may return a different list than the one the replacement was constructed with.
Beware that this normalization does not remove redundant no-op copied-string
pieces at the start or end of the replacement. That is the job of the stricter
replacement focusing operation implemented by string-replacement-focus. For
example, consider a replacement that starts at position 0, ends at position n,
and contains a single (copied-string 0 n) piece. This replacement is functionally a
no-op that makes no changes at all to the string. Construction does not remove the
copied-string piece, even though it behaves identically to an empty string replacement.
Focusing the replacement, on the other hand, does remove the copied string piece —
procedure
(string-replacement-start replacement) → natural?
replacement : string-replacement?
procedure
(string-replacement-original-end replacement) → natural?
replacement : string-replacement?
procedure
(string-replacement-original-span replacement) → natural?
replacement : string-replacement?
procedure
(string-replacement-new-end replacement) → natural?
replacement : string-replacement?
procedure
(string-replacement-new-span replacement) → natural?
replacement : string-replacement?
procedure
(string-replacement-contents replacement)
→ (listof string-piece?) replacement : string-replacement?
procedure
(string-replacement-preserved-locations replacement)
→ range-set? replacement : string-replacement?
This is used by Resyntax to determine whether or not a replacement preserves comments: if a replacement’s unpreserved locations have any overlap with source-comment-locations for the source being edited, then the replacement drops comments and is discarded by Resyntax.
procedure
(string-replacement-overlaps? replacement other-replacement) → boolean? replacement : string-replacement? other-replacement : string-replacement?
Overlap detection is conservatively imperfect. There exist string replacements that can be applied
together safely, but which string-replacement-overlaps? reports #true for. However,
there are never cases where string-replacement-overlaps? issues a false negative —
procedure
(string-replacement-union replacement1 replacement2) → string-replacement? replacement1 : string-replacement? replacement2 : string-replacement?
A quirk of the current implementation is that empty replacements are not treated specially. The union of an empty replacement at position 0 and a replacement at position n will produce an expanded replacement that starts at position 0. A no-op copied-string piece that copies the contents between 0 and n will be included. This results in a replacement that behaves identically, but which reports an earlier start position and larger spans from string-replacement-original-span and string-replacement-new-span. Like the deficiencies of string-replacement-overlaps?, this could be fixed by using a range map representation for string replacements instead of a single range representation.
Note that even for non-overlapping replacements, applying the union of two string replacements is
not the same as applying one replacement and then applying the other. This is because of
copied strings —
When Resyntax wants to apply multiple string replacements at once, it always combines their replacements into a single union replacement. Conflicting replacements are dropped; rather than try to apply them in a second stage, Resyntax throws them out and re-analyzes the entire modified source file instead to generate fresh suggested replacements. As a result, Resyntax has to decide which replacements it wants to apply in each analysis round before it can begin the next analysis round. The result of each analysis round edits the source code in-memory using modified-source so that Resyntax can interleave editing and analysis in this manner without actually committing its edits to the filesystem.
Note that because of the quirk with how string-replacement-union handles empty replacements, this implies that a replacement produced by union-into-string-replacement always starts at position 0 and includes a large no-op copied-string piece between position 0 and the lowest-position replacement that was included in the union.
procedure
(string-replacement-focus replacement original-string [ #:preserve-start preserve-start #:preserve-end preserve-end]) → string-replacement? replacement : string-replacement? original-string : string?
preserve-start : (or/c exact-nonnegative-integer? #false) = #false
preserve-end : (or/c exact-nonnegative-integer? #false) = #false
This operation is called replacement focusing, and is more aggressive than the normalization performed automatically by the string-replacement constructor. This is the low-level mechanism used by Resyntax to implement the replacement focusing behavior described in Narrowing the Focus of Replacements.
procedure
(string-replacement-new-text replacement original-string) → immutable-string? replacement : string-replacement? original-string : string?
procedure
(string-replacement-apply replacement string) → immutable-string? replacement : string-replacement? string : string?
procedure
(string-replacement-apply-to-file! replacement path) → void? replacement : string-replacement? path : path-string?
4.6.1 String Pieces
A string piece describes a segment of text, either brand new or copied from some original string. String pieces primarily serve as the contents of a string replacement’s replaced region, but they are also occasionally useful on their own as standalone descriptions of text.
procedure
(string-piece? v) → boolean?
v : any/c
procedure
(inserted-string? v) → boolean?
v : any/c
procedure
(inserted-string contents) → inserted-string?
contents : string?
procedure
(inserted-string-contents piece) → immutable-string?
piece : inserted-string?
procedure
(copied-string? v) → boolean?
v : any/c
procedure
(copied-string start end) → copied-string?
start : natural? end : natural?
procedure
(copied-string-start piece) → natural?
piece : copied-string?
procedure
(copied-string-end piece) → natural?
piece : copied-string?
procedure
(string-piece-span piece) → exact-nonnegative-integer?
piece : string-piece?