Skip to content

Commit 3aab87e

Browse files
authored
Implement syntax paths (#481)
* Implement syntax paths This reworks the logic Resyntax uses for detecting if two syntax objects were originally neighbors. The new implementation uses a notion of *syntax paths*, which are essentially lists of indices that can be used to traverse from root syntax objects down into deeply nested children. Given two syntax paths, it's trivial to tell if they point to neighboring syntax objects. Resyntax now records the original syntax path of every subform of a syntax object it's analyzing before expanding it. Because syntax properties propagate through the macro expander automatically, this property can be used to track where a syntax object in a suggested replacement came from. * Explain something confusing about syntax paths
1 parent b162c47 commit 3aab87e

File tree

4 files changed

+311
-71
lines changed

4 files changed

+311
-71
lines changed

base.rkt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,14 @@
105105

106106
(define (refactoring-rule-refactor rule syntax source)
107107

108-
;; Before refactoring the input syntax, we do two things: create a new scope and add it, and
109-
;; traverse the syntax object making a note of each subform's original neighbors. Combined,
110-
;; these two things allow us to tell when two neighboring subforms within the output syntax object
111-
;; are originally from the input and were originally next to each other in the input. This allows
108+
;; Before refactoring the input syntax, we create a new scope and add it. Combined with the code in
109+
;; resyntax/private/source which marks the original path of every syntax object before expansion,
110+
;; this allows us to tell when two neighboring subforms within the output syntax object are
111+
;; originally from the input and were originally next to each other in the input. This allows
112112
;; Resyntax to preserve any formatting and comments between those two subform when rendering the
113113
;; resulting syntax replacement into a string transformation.
114114
(define rule-introduction-scope (make-syntax-introducer))
115-
(define prepared-syntax (rule-introduction-scope (syntax-mark-original-neighbors syntax)))
115+
(define prepared-syntax (rule-introduction-scope syntax))
116116

117117
(option-map
118118
((refactoring-rule-transformer rule) prepared-syntax)

private/source.rkt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
resyntax/private/fully-expanded-syntax
5252
resyntax/private/linemap
5353
resyntax/private/logger
54+
resyntax/private/syntax-neighbors
5455
syntax/id-table
5556
syntax/modread
5657
syntax/parse)
@@ -108,7 +109,7 @@
108109
(define (read-from-input)
109110
(port-count-lines! (current-input-port))
110111
(with-module-reading-parameterization read-syntax))
111-
(with-input-from-source code read-from-input))
112+
(syntax-label-original-paths (with-input-from-source code read-from-input)))
112113

113114

114115
(define/guard (source-path code)
@@ -133,6 +134,7 @@
133134
[current-namespace ns])
134135
(define code-linemap (string-linemap (source->string code)))
135136
(define program-stx (source-read-syntax code))
137+
(log-resyntax-debug "original syntax:\n ~a" program-stx)
136138
(define program-source-name (syntax-source program-stx))
137139
(define current-expand-observe (dynamic-require ''#%expobs 'current-expand-observe))
138140
(define original-visits (make-vector-builder))
@@ -235,6 +237,16 @@
235237
(sorting syntax-source-location<=> #:key syntax-source-location)
236238
#:into into-list))
237239

240+
(for ([visit (in-list visited)])
241+
(log-resyntax-debug (string-append "visited ~a:\n"
242+
" form: ~a\n"
243+
" original path property: ~a\n"
244+
" tracked origin: ~a")
245+
(syntax-original-path visit)
246+
visit
247+
(syntax-property visit 'original-syntax-path)
248+
(syntax-property visit 'origin)))
249+
238250
(source-code-analysis #:code code
239251
#:visited-forms visited
240252
#:expansion-time-output output

private/syntax-neighbors.rkt

Lines changed: 62 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,18 @@
1515

1616
(provide
1717
(contract-out
18-
[syntax-original-leading-neighbor (-> syntax? (or/c syntax? #false))]
19-
[syntax-original-trailing-neighbor (-> syntax? (or/c syntax? #false))]
18+
[syntax-original-path (-> syntax? (or/c syntax-path? #false))]
19+
[syntax-label-original-paths (-> syntax? syntax?)]
2020
[syntax-originally-neighbors? (-> syntax? syntax? boolean?)]
21-
[syntax-mark-original-neighbors (-> syntax? syntax?)]
2221
[syntax-extract-originals-from-pair (-> syntax? syntax? (values syntax? syntax?))]))
2322

2423

2524
(require guard
25+
racket/match
26+
racket/struct
2627
racket/syntax-srcloc
2728
resyntax/private/logger
29+
resyntax/private/syntax-path
2830
syntax/parse
2931
syntax/parse/experimental/template)
3032

@@ -38,43 +40,27 @@
3840
;@----------------------------------------------------------------------------------------------------
3941

4042

41-
(define (syntax-mark-original-neighbors stx)
42-
(syntax-parse stx
43-
[(~and (subform ...+) (_ trailing-neighbor ...) (leading-neighbor ... _))
44-
(define leading-neighbors (cons #false (attribute leading-neighbor)))
45-
(define trailing-neighbors (append (attribute trailing-neighbor) (list #false)))
46-
(define results
47-
(for/list ([leading (in-list leading-neighbors)]
48-
[trailing (in-list trailing-neighbors)]
49-
[subform-stx (in-list (attribute subform))])
50-
(define leading-pos (and leading (syntax-position leading)))
51-
(define trailing-pos (and trailing (syntax-position trailing)))
52-
(define subform-pos (syntax-position subform-stx))
53-
(mark-neighbors (syntax-mark-original-neighbors subform-stx)
54-
#:leading-neighbor (and leading (< leading-pos subform-pos) leading)
55-
#:trailing-neighbor (and trailing (< subform-pos trailing-pos) trailing))))
56-
(datum->syntax stx results stx stx)]
57-
[_ stx]))
43+
(define original-syntax-path-key 'original-syntax-path)
5844

5945

60-
(define (mark-neighbors stx #:leading-neighbor leading-stx #:trailing-neighbor trailing-stx)
61-
(define stx-with-leading
62-
(if leading-stx
63-
(syntax-property stx 'original-leading-neighbor leading-stx)
64-
stx))
65-
(if trailing-stx
66-
(syntax-property stx-with-leading
67-
'original-trailing-neighbor
68-
trailing-stx)
69-
stx-with-leading))
46+
(define (syntax-label-original-paths stx)
47+
(syntax-label-paths stx original-syntax-path-key))
7048

7149

72-
(define (syntax-original-leading-neighbor stx)
73-
(syntax-property stx 'original-leading-neighbor))
74-
75-
76-
(define (syntax-original-trailing-neighbor stx)
77-
(syntax-property stx 'original-trailing-neighbor))
50+
(define (syntax-original-path stx)
51+
; The property value will be a cons tree if a macro produced a syntax object with the path property
52+
; set. The main way this occurs is via `(begin x ...)`, as each of the `x` subforms counts as an
53+
; "expansion" of the surrounding `(begin ...)` and therefore has its properties merged. In such a
54+
; case, each `x` counts as the "result" and the `(begin ...)` counts as the "original", so if an
55+
; `x` and the `(begin ...)` both have their paths set, the resulting property path will be
56+
; `(cons <path-of-x> <path-of-(begin...)>)`. We therefore want to pick the *head* of any cons cells
57+
; we encounter when looking up the original syntax path property value. There might be other cases
58+
; where we want to look at the tail for some reason, but if those cases exist I haven't found them
59+
; yet and they don't cause any of Resyntax's tests to fail.
60+
(let loop ([possible-cons-tree (syntax-property stx original-syntax-path-key)])
61+
(if (pair? possible-cons-tree)
62+
(loop (car possible-cons-tree))
63+
possible-cons-tree)))
7864

7965

8066
(define (syntax-extract-originals-from-pair left-stx right-stx)
@@ -89,12 +75,12 @@
8975
(define (syntax-originally-neighbors? left-stx* right-stx*)
9076
(define-values (left-stx right-stx) (syntax-extract-originals-from-pair left-stx* right-stx*))
9177
(guarded-block
92-
(define left-trailer (syntax-original-trailing-neighbor left-stx))
93-
(define right-leader (syntax-original-leading-neighbor right-stx))
78+
(define left-path (syntax-original-path left-stx))
79+
(define right-path (syntax-original-path right-stx))
9480
;; If either of the above is missing, then they're not neighbors. We log a debug message in that
95-
;; case aide in debugging test failures caused by dropped comments.
96-
(guard left-trailer #:else
97-
(log-resyntax-debug (string-append "not neighbors because left-trailer is missing\n"
81+
;; case to aide in debugging test failures caused by dropped comments.
82+
(guard left-path #:else
83+
(log-resyntax-debug (string-append "not neighbors because left-path is missing\n"
9884
" original left syntax: ~a\n"
9985
" original right syntax: ~a\n"
10086
" replacement left syntax: ~a\n"
@@ -104,8 +90,8 @@
10490
(syntax->datum left-stx*)
10591
(syntax->datum right-stx*))
10692
#false)
107-
(guard right-leader #:else
108-
(log-resyntax-debug (string-append "not neighbors because right-leader is missing\n"
93+
(guard right-path #:else
94+
(log-resyntax-debug (string-append "not neighbors because right-path is missing\n"
10995
" original left syntax: ~a\n"
11096
" original right syntax: ~a\n"
11197
" replacement left syntax: ~a\n"
@@ -115,34 +101,45 @@
115101
(syntax->datum left-stx*)
116102
(syntax->datum right-stx*))
117103
#false)
118-
(define left-srcloc (syntax-srcloc left-stx))
119-
(define left-trailer-srcloc (syntax-srcloc left-trailer))
120-
(define right-srcloc (syntax-srcloc right-stx))
121-
(define right-leader-srcloc (syntax-srcloc right-leader))
122-
(guard (and left-srcloc left-trailer-srcloc right-srcloc right-leader-srcloc) #:else #false)
123-
(and (equal? left-trailer-srcloc right-srcloc) (equal? right-leader-srcloc left-srcloc))))
104+
(define neighbors? (syntax-path-neighbors? left-path right-path))
105+
(unless neighbors?
106+
(log-resyntax-debug (string-append "not neighbors because syntax-path-neighbors? says so\n"
107+
" original left path: ~a\n"
108+
" original right path: ~a\n"
109+
" original left syntax: ~a\n"
110+
" original right syntax: ~a\n"
111+
" replacement left syntax: ~a\n"
112+
" replacement right syntax: ~a")
113+
left-path
114+
right-path
115+
(syntax->datum left-stx)
116+
(syntax->datum right-stx)
117+
(syntax->datum left-stx*)
118+
(syntax->datum right-stx*)))
119+
neighbors?))
124120

125121

126122
(module+ test
127-
(test-case "syntax-mark-original-neighbors"
123+
(test-case "syntax-originally-neighbors?"
128124
(define stx #'(foo (a b c) bar (baz)))
129-
(define marked (syntax-mark-original-neighbors stx))
130-
(check-equal? (syntax->datum marked) (syntax->datum stx))
131-
(define/with-syntax (foo* (a* b* c*) bar* (baz*)) marked)
132-
(check-false (syntax-original-leading-neighbor #'foo*))
133-
(check-equal? (syntax->datum (syntax-original-trailing-neighbor #'foo*)) '(a b c))
134-
(check-false (syntax-original-leading-neighbor #'a*))
135-
(check-equal? (syntax->datum (syntax-original-trailing-neighbor #'a*)) 'b)
136-
(check-equal? (syntax->datum (syntax-original-leading-neighbor #'b*)) 'a)
137-
(check-equal? (syntax->datum (syntax-original-trailing-neighbor #'b*)) 'c)
138-
(check-equal? (syntax->datum (syntax-original-leading-neighbor #'c*)) 'b)
139-
(check-false (syntax-original-trailing-neighbor #'c*))
140-
(check-equal? (syntax->datum (syntax-original-leading-neighbor #'bar*)) '(a b c))
141-
(check-equal? (syntax->datum (syntax-original-trailing-neighbor #'bar*)) '(baz))
142-
(check-false (syntax-original-leading-neighbor #'baz*))
143-
(check-false (syntax-original-trailing-neighbor #'baz*))
125+
(define labeled (syntax-label-original-paths stx))
126+
(check-equal? (syntax->datum labeled) (syntax->datum stx))
127+
(define/with-syntax (foo* (a* b* c*) bar* (baz*)) labeled)
144128
(check-false (syntax-originally-neighbors? #'foo* #'b*))
145129
(check-true (syntax-originally-neighbors? #'a* #'b*))
146130
(check-true (syntax-originally-neighbors? #'b* #'c*))
147131
(check-false (syntax-originally-neighbors? #'c* #'bar*))
148132
(check-false (syntax-originally-neighbors? #'bar* #'baz*))))
133+
134+
135+
(define (improper-list-drop-tail improper-list)
136+
(cons (car improper-list)
137+
(let loop ([improper-list (cdr improper-list)])
138+
(if (pair? improper-list)
139+
(cons (car improper-list) (loop (cdr improper-list)))
140+
'()))))
141+
142+
143+
(module+ test
144+
(test-case "improper-list-drop-tail"
145+
(check-equal? (improper-list-drop-tail '(1 2 3 . 4)) '(1 2 3))))

0 commit comments

Comments
 (0)