Skip to content

Commit d93a51f

Browse files
authored
Preserve metadata during syntax traversal (#489)
1 parent 30963bb commit d93a51f

File tree

3 files changed

+124
-27
lines changed

3 files changed

+124
-27
lines changed

default-recommendations/match-shortcuts-test.rkt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,31 @@ test: "match patterns using ? with a lambda cannot be simplified when under elli
170170
(list y*)]
171171
[_ 'no-match]))
172172
------------------------------
173+
174+
test: "match patterns using ? with a commented lambda can be simplified with #:when clauses"
175+
------------------------------
176+
(define (foo x)
177+
(match x
178+
[(? (λ (y) (< y
179+
10
180+
; comment
181+
20)) y*)
182+
(list y*)]
183+
[_ 'no-match]))
184+
(foo 5)
185+
(foo 100)
186+
------------------------------
187+
------------------------------
188+
(define (foo x)
189+
(match x
190+
[y*
191+
#:when (< y*
192+
10
193+
; comment
194+
20)
195+
(list y*)]
196+
[_ 'no-match]))
197+
(foo 5)
198+
(foo 100)
199+
------------------------------
200+

private/syntax-replacement.rkt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
racket/match
3333
racket/pretty
3434
racket/sequence
35+
racket/stream
3536
racket/string
3637
rebellion/base/comparator
3738
rebellion/collection/range-set
@@ -42,6 +43,7 @@
4243
resyntax/private/string-indent
4344
resyntax/private/string-replacement
4445
resyntax/private/syntax-neighbors
46+
(only-in resyntax/private/syntax-traversal syntax-search-everything)
4547
syntax/parse
4648
syntax/parse/experimental/template
4749
(only-in rebellion/base/range closed-open-range)
@@ -71,7 +73,7 @@
7173
(guard (or focused? (not (syntax-property stx 'focus-replacement-on))) #:else
7274
(log-resyntax-debug "focusing in on ~a" stx)
7375
(list (focus (pieces stx #:focused? #true))))
74-
(guard (not (syntax-original? stx)) #:else
76+
(guard (not (syntax-completely-original? stx)) #:else
7577
(log-resyntax-debug "copying original syntax ~a" stx)
7678
(define start (sub1 (syntax-position stx)))
7779
(define end (+ start (syntax-span stx)))
@@ -298,3 +300,8 @@
298300
(define (syntax-source-range stx)
299301
(define start (sub1 (syntax-position stx)))
300302
(closed-open-range start (+ start (syntax-span stx)) #:comparator natural<=>))
303+
304+
305+
(define (syntax-completely-original? stx)
306+
(for/and ([subform (in-stream (syntax-search-everything stx))])
307+
(syntax-original? subform)))

private/syntax-traversal.rkt

Lines changed: 88 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@
88
expression-directly-enclosing
99
syntax-find-first
1010
syntax-search
11+
syntax-search-everything
1112
syntax-traverse)
1213

1314

1415
(require (for-syntax racket/base
15-
resyntax/private/more-syntax-parse-classes
16-
syntax/parse)
17-
racket/match
16+
resyntax/private/more-syntax-parse-classes)
1817
racket/sequence
1918
racket/stream
2019
syntax/parse
@@ -55,33 +54,43 @@
5554

5655

5756
(define-syntax-parse-rule
58-
(syntax-search stx-expr option:syntax-parse-option ... clause:syntax-search-clause ...)
57+
(syntax-search stx-expr
58+
(~optional (~seq #:skip-root? skip-root?) #:defaults ([skip-root? #'#false]))
59+
option:syntax-parse-option ...
60+
clause:syntax-search-clause ...)
5961
#:declare stx-expr (expr/c #'syntax?)
60-
(let ()
62+
(let ([skip-root-id skip-root?])
6163
(define-syntax-class search-case
6264
#:attributes (output-stream)
6365
(~@ . option) ...
6466
(pattern clause.syntax-pattern (~@ . clause.directive) ...
6567
#:attr output-stream clause.output-stream)
6668
...)
67-
(let loop ([stx stx-expr.c])
69+
(let loop ([stx stx-expr.c] [root? #true])
6870
(stream-lazy
6971
(syntax-parse stx
70-
[(~var matched search-case) (attribute matched.output-stream)]
72+
[child
73+
#:when (not (and skip-root-id root?))
74+
#:with (~var matched search-case) (attribute child)
75+
(attribute matched.output-stream)]
7176
[(part (... ...))
7277
#:cut
7378
(apply stream-append
7479
(for/list ([part-stx (in-list (attribute part))])
75-
(loop part-stx)))]
80+
(loop part-stx #false)))]
7681
[(part (... ...+) . tail-part)
7782
#:cut
7883
(stream-append (apply stream-append
7984
(for/list ([part-stx (in-list (attribute part))])
80-
(loop part-stx)))
81-
(loop #'tail-part))]
85+
(loop part-stx #false)))
86+
(loop #'tail-part #false))]
8287
[_ (stream)])))))
8388

8489

90+
(define (syntax-search-everything stx)
91+
(stream-cons stx (syntax-search stx #:skip-root? #true [_ (syntax-search-everything this-syntax)])))
92+
93+
8594
(module+ test
8695
(test-case "syntax-search"
8796
(define stx
@@ -100,9 +109,9 @@
100109

101110

102111
(define-syntax-parse-rule (syntax-find-first stx-expr
103-
option:syntax-parse-option ...
104-
syntax-pattern
105-
directive:syntax-parse-pattern-directive ...)
112+
option:syntax-parse-option ...
113+
syntax-pattern
114+
directive:syntax-parse-pattern-directive ...)
106115
(let ()
107116
(define results (syntax-search stx-expr (~@ . option) ... [syntax-pattern (~@ . directive) ...]))
108117
(and (not (stream-empty? results)) (stream-first results))))
@@ -123,27 +132,40 @@
123132

124133
(define-syntax-parse-rule
125134
(syntax-traverse (~var stx-expr (expr/c #'syntax?))
135+
(~optional (~seq #:skip-root? skip-root?) #:defaults ([skip-root? #'#false]))
126136
option:syntax-parse-option ...
127137
[clause-pattern directive:syntax-parse-pattern-directive ... clause-body:expr ...+] ...)
128-
(let ()
138+
(let ([skip-root-id skip-root?])
129139
(define-syntax-class traversal-case
130140
#:attributes (traversed)
131141
(~@ . option) ...
132142
(pattern clause-pattern (~@ . directive) ...
133143
#:attr traversed (let () clause-body ...)) ...)
134-
(let loop ([stx stx-expr.c])
144+
(let loop ([stx stx-expr.c] [root? #true])
145+
146+
(define (rewrap-datum datum)
147+
(datum->syntax stx datum stx stx))
148+
135149
(syntax-parse stx
136-
[(~var matched traversal-case) (attribute matched.traversed)]
150+
151+
[child
152+
#:when (not (and skip-root-id root?))
153+
#:with (~var matched traversal-case) (attribute child)
154+
(define case-scope (make-syntax-introducer))
155+
(case-scope (attribute matched.traversed) 'add)]
137156

138157
[(part (... ...))
139158
#:cut
140-
#:with (traversed-part (... ...)) (map loop (attribute part))
141-
#'(traversed-part (... ...))]
159+
(rewrap-datum
160+
(for/list ([child (in-list (attribute part))])
161+
(loop child #false)))]
142162
[(part (... ...+) . tail-part)
143163
#:cut
144-
#:with (traversed-part (... ...)) (map loop (attribute part))
145-
#:with traversed-tail (loop #'tail-part)
146-
#'(traversed-part (... ...) . traversed-tail)]
164+
(define traversed-children
165+
(for/list ([child (in-list (attribute part))])
166+
(loop child #false)))
167+
(define traversed-tail (loop #'tail-part #false))
168+
(rewrap-datum (append traversed-children traversed-tail))]
147169
[_ stx]))))
148170

149171

@@ -156,14 +178,54 @@
156178
(cons a b))
157179
(cons c d)))
158180
(define actual
159-
(syntax->datum
160-
(syntax-traverse stx
161-
#:literals (cons)
162-
[(cons _ _) #'CONS-EXPRESSION])))
181+
(syntax-traverse stx
182+
#:literals (cons)
183+
[(cons _ _) #'CONS-EXPRESSION]))
163184
(define expected
164185
'(define (foo)
165186
CONS-EXPRESSION
166187
(define (bar)
167188
CONS-EXPRESSION)
168189
CONS-EXPRESSION))
169-
(check-equal? actual expected)))
190+
(check-equal? (syntax->datum actual) expected))
191+
192+
(test-case "syntax-traverse #:skip-root? true"
193+
(define stx #'(a b (c d) e))
194+
(define actual
195+
(syntax-traverse stx
196+
#:skip-root? #true
197+
[(_ ...) #'LIST]))
198+
(check-equal? (syntax->datum actual) '(a b LIST e)))
199+
200+
(test-case "syntax-traverse #:skip-root? true doesn't execute directives on root"
201+
(define stx #'(a b (c d) e))
202+
(define execution-count 0)
203+
(syntax-traverse stx
204+
#:skip-root? #true
205+
[(_ ...)
206+
#:do [(set! execution-count (add1 execution-count))]
207+
#'LIST])
208+
(check-equal? execution-count 1))
209+
210+
(test-case "syntax-traverse #:skip-root? false"
211+
(define stx #'(a b (c d) e))
212+
(define actual
213+
(syntax-traverse stx
214+
#:skip-root? #false
215+
[(_ ...) #'LIST]))
216+
(check-equal? (syntax->datum actual) 'LIST))
217+
218+
(test-case "syntax-traverse originality"
219+
(define stx (read-syntax #false (open-input-string "(1 2 (a b) 3 4)")))
220+
(check-true (syntax-original? stx))
221+
(define traversed-stx
222+
(syntax-traverse stx
223+
[(_ id:id) (attribute id)]))
224+
(check-equal? (syntax->datum traversed-stx) '(1 2 b 3 4))
225+
(check-true (syntax-original? traversed-stx))
226+
(define/syntax-parse (1* 2* b* 3* 4*) traversed-stx)
227+
(check-true (syntax-original? #'1*))
228+
(check-true (syntax-original? #'2*))
229+
(check-false (syntax-original? #'b*))
230+
(check-true (syntax-original? #'3*))
231+
(check-true (syntax-original? #'4*))))

0 commit comments

Comments
 (0)