Skip to content

Commit b414b5d

Browse files
authored
Document some replacement metafunctions (#356)
Partial fix for #349. The `~focus-replacement-on` metafunction is not yet documented since it's trickier to explain.
1 parent f3770b7 commit b414b5d

File tree

1 file changed

+101
-0
lines changed

1 file changed

+101
-0
lines changed

main.scrbl

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,107 @@ refactoring rules.
230230
(void)))}
231231

232232

233+
@subsection{Exercising Fine Control Over Comments}
234+
235+
236+
Writing a rule with @racket[define-refactoring-rule] is usually enough for Resyntax to handle
237+
commented code without issue, but in certain cases more precise control is desired. For instance,
238+
consider the @racketidfont{nested-or-to-flat-or} rule from earlier:
239+
240+
@(racketblock
241+
(define-refactoring-rule nested-or-to-flat-or
242+
#:description "This nested `or` expression can be flattened."
243+
#:literals (or)
244+
(or a (or b c))
245+
(or a b c)))
246+
247+
As-is, this rule will @emph{fail} to refactor the following code:
248+
249+
@(racketblock
250+
(or (foo ...)
251+
(code:comment @#,elem{If that doesn't work, fall back to other approaches})
252+
(or (bar ...)
253+
(baz ...))))
254+
255+
Resyntax rejects the rule because applying it would produce this code, which loses the comment:
256+
257+
@(racketblock
258+
(or (foo ...)
259+
(bar ...)
260+
(baz ...)))
261+
262+
Resyntax is unable to preserve the comment automatically. Resyntax can preserve some comments without
263+
programmer effort, but only in specific circumstances:
264+
265+
@itemlist[
266+
@item{Comments @emph{within} expressions that the rule left unchanged are preserved. If the comment
267+
were inside @racket[(foo ...)], @racket[(bar ...)], or @racket[(baz ...)], it would have been kept.}
268+
269+
@item{Comments @emph{between} unchanged expressions are similarly preserved. If the comment were
270+
between @racket[(bar ...)] and @racket[(baz ...)], it would have been kept.}]
271+
272+
To fix this issue, rule authors can inject some extra markup into their suggested replacements using
273+
@tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")]{template metafunctions} provided by Resyntax. In
274+
the case of @racketidfont{nested-or-to-flat-or}, we can use the @racket[~splicing-replacement]
275+
metafunction to indicate that the nested @racket[or] expression should be considered @emph{replaced}
276+
by its nested subterms:
277+
278+
@(racketblock
279+
(define-refactoring-rule nested-or-to-flat-or
280+
#:description "This nested `or` expression can be flattened."
281+
#:literals (or)
282+
(or a (~and nested-or (or b c)))
283+
#:with (nested-subterm ...) #'(~splicing-replacement (b c) #:original nested-or)
284+
(or a nested-subterm ...)))
285+
286+
This adds @tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{syntax properties} to the nested
287+
subterms that allow Resyntax to preserve the comment, producing this output:
288+
289+
@(racketblock
290+
(or (foo ...)
291+
(code:comment @#,elem{If that doesn't work, fall back to other approaches})
292+
(bar ...)
293+
(baz ...)))
294+
295+
When Resyntax sees that the @racket[(bar ...)] nested subterm comes immediately after the
296+
@racket[(foo ...)] subterm, it notices that @racket[(bar ...)] has been annotated with replacement
297+
properties. Then Resyntax observes that @racket[(bar ...)] is the first expression of a sequence of
298+
expressions that replaces the @racket[or] expression which originally followed @racket[(foo ...)].
299+
Based on this observation, Resyntax decides to preserve whatever text was originally between
300+
@racket[(foo ...)] and the nested @racket[or] expression. This mechanism, exposed via
301+
@racket[~replacement] and @racket[~splicing-replacement], offers a means for refactoring rules to
302+
guide Resyntax's internal comment preservation system when the default behavior is not sufficient.
303+
304+
@defform[#:kind "template metafunction"
305+
(~replacement replacement-form original)
306+
#:grammar
307+
([original
308+
(code:line #:original original-form)
309+
(code:line #:original-splice (original-form ...))])]{
310+
A @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")]{template metafunction} for use in
311+
@tech{refactoring rules}. The result of the metafunction is just the @racket[#'replacement-form]
312+
syntax object, except with some
313+
@tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{syntax properties} added. Those
314+
properties inform Resyntax that this syntax object should be considered a replacement for
315+
@racket[original-form] (or in the splicing case, for the unparenthesized sequence
316+
@racket[original-form ...]). Resyntax uses this information to preserve comments and formatting near
317+
the original form(s).}
318+
319+
@defform[#:kind "template metafunction"
320+
(~splicing-replacement (replacement-form ...) original)
321+
#:grammar
322+
([original
323+
(code:line #:original original-form)
324+
(code:line #:original-splice (original-form ...))])]{
325+
A @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")]{template metafunction} for use in
326+
@tech{refactoring rules}. The result of the metafunction is the syntax object
327+
@racket[#'(replacement-form ...)], except with some
328+
@tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{syntax properties} added. Those
329+
properties inform Resyntax that the replacement syntax objects --- as an unparenthesized sequence ---
330+
should be considered a replacement for @racket[original-form] (or @racket[original-form ...]).
331+
Resyntax uses this information to preserve comments and formatting near the original form(s).}
332+
333+
233334
@subsection{Resyntax's Default Rules}
234335
@defmodule[resyntax/default-recommendations]
235336

0 commit comments

Comments
 (0)