4.3 Syntax Paths
| (require resyntax/grimoire/syntax-path) | package: resyntax |
A syntax path identifies the location of a subform within a syntax object. Syntax paths
are sequences of zero-based indices: the root path, which contains no indices, refers to an
entire syntax object, and a path whose first element is i refers to a location within the
syntax object’s i-th child. Syntax paths are similar in spirit to filesystem paths, and they
print in a filesystem-like notation —
The children of a syntax object are determined by the shape of its datum:
The children of a pair-based form are the elements of its normalized shape: the proper list obtained by treating the trailing atom of an improper list, if any, as a final element. Only the form’s own pair structure is normalized —
nested forms remain distinct children — but how the underlying pairs and syntax objects nest has no effect on paths: #’(a b c), #’(a . (b . (c . ()))), #’(a . (b c)), #’(a b . c), and #’(a . (b . c)) all have three children, and in each case the child at index 2 is #'c. The children of a vector are its elements, in order.
A box has a single child, its contents, at index 0.
The children of a prefab struct are its fields, in order.
Hash datums cannot be traversed by syntax paths. Operations that would need to traverse a hash raise contract errors instead. More about this constraint is explained in Original Syntax Paths and Formatting Preservation.
All other datums have no children.
4.3.1 Original Syntax Paths and Formatting Preservation
When Resyntax calls the Racket reader to turn source code into unexpanded syntax objects using source-read-syntax, Resyntax recursively labels each syntax object with its original syntax path. This label is attached to each syntax object via a syntax property named 'original-syntax-path.
As these syntax objects are expanded by the Racket macro expander and further manipulated by refactoring rules, subforms get transformed and moved, but they preserve their original syntax properties. Resyntax then inspects these properties on the refactored syntax objects produced by rules in order to determine where each syntax object originated structurally, in addition to inspecting source locations to determine where it originated textually. Resyntax uses this information to determine when refactored code contains unchanged subexpressions whose text should be copied from the input rather than reproduced wholesale and reformatted. This mechanism is what powers the automatic comment preservation described in Exercising Fine Control Over Comments, and the template metafunctions described in that section give rule authors a way to guide it.
Unchanged syntax objects have their original text copied by Resyntax, and if two unchanged syntax objects start and end adjacent to each other (and without swapping their order) then the original text between them is also copied. This allows Resyntax to minimize the amount of text that a refactoring has to change. However, in order for this process to work, Resyntax makes a few assumptions about the relationship between syntax paths and the source locations of reader-produced syntax objects. These assumptions are:
If one syntax path is a prefix of another, then the source location of a syntax object originally at the first path (the prefix path) should fully contain the source location of a syntax object originally at the second path. This need not be a proper subset relation: it’s acceptable for the two syntax objects to have the exact same source location. That allowance doesn’t affect code written in #lang racket, but it matters for other languages which sometimes use "invisible" wrappers around syntax objects to reflect grouping implied by how the text was originally parsed by the language’s reader.
If two syntax paths are disjoint, meaning neither is a prefix of the other, then the source locations of two syntax objects originally at those paths should contain no overlap (but they may be adjacent). Furthermore, the lesser syntax path (in the sense of syntax-path<=>) should correspond to the source location that occurs earlier in the source code (by character position).
It is the second of these two requirements that prevents Resyntax from traversing literal hash datums. A hash datum such as #hash((b . 3) (a . 1) ("foo" . 2)), when parsed by the Racket reader, does not preserve source ordering in the resulting hash datum’s iteration order. Two literal hash datums with the same keys will always iterate in the same order when inspected with syntax-e. As a result, Resyntax cannot uphold the assumptions it makes about source locations and syntax paths, and cannot preserve text between adjacent hash entries correctly. For this reason, Resyntax does not allow editing expressions inside hash datums.
4.3.2 Basic Syntax Path Operations
procedure
(syntax-path? v) → boolean?
v : any/c
procedure
(root-syntax-path? v) → boolean?
v : any/c
procedure
(child-syntax-path? v) → boolean?
v : any/c
value
procedure
(syntax-path elements) → syntax-path?
elements : (sequence/c exact-nonnegative-integer?)
procedure
(syntax-path-elements path)
→ (treelist/c exact-nonnegative-integer?) path : syntax-path?
procedure
(syntax-path-add path element) → syntax-path?
path : syntax-path? element : exact-nonnegative-integer?
procedure
(syntax-path-parent path) → syntax-path?
path : child-syntax-path?
procedure
(syntax-path-last-element path) → exact-nonnegative-integer?
path : child-syntax-path?
procedure
(syntax-path-next-neighbor path) → (or/c syntax-path? #false)
path : syntax-path?
procedure
(syntax-path-neighbors? leading-path trailing-path) → boolean? leading-path : syntax-path? trailing-path : syntax-path?
procedure
(syntax-path-remove-prefix path prefix) → syntax-path?
path : syntax-path? prefix : syntax-path?
value
procedure
(syntax-path->string path) → immutable-string?
path : syntax-path?
procedure
(string->syntax-path str) → syntax-path?
str : string?
4.3.3 Operating on Syntax Objects with Syntax Paths
procedure
(syntax-ref stx path) → syntax?
stx : syntax? path : syntax-path?
procedure
(syntax-contains-path? stx path) → boolean?
stx : syntax? path : syntax-path?
procedure
(syntax-set stx path new-subform) → syntax?
stx : syntax? path : syntax-path? new-subform : syntax?
procedure
(syntax-remove-splice stx path children-count) → syntax? stx : syntax? path : child-syntax-path? children-count : exact-nonnegative-integer?
procedure
(syntax-insert-splice stx path new-children) → syntax?
stx : syntax? path : child-syntax-path? new-children : (sequence/c syntax?)
procedure
(syntax-label-paths stx property-name) → syntax?
stx : syntax? property-name : symbol?
procedure
(in-syntax-paths stx [#:base-path base-path])
→ (sequence/c syntax-path?) stx : syntax? base-path : syntax-path? = root-syntax-path