Skip to content

Commit 919057b

Browse files
authored
Rewrite named lets into for/or and for/and (#466)
Closes #454.
1 parent 463d1cb commit 919057b

File tree

5 files changed

+157
-16
lines changed

5 files changed

+157
-16
lines changed

base.rkt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,17 @@
2929
(-> refactoring-rule? syntax? source? (option/c syntax-replacement?))])))
3030

3131

32-
(require (for-syntax racket/base racket/syntax resyntax/private/more-syntax-parse-classes)
32+
(require (for-syntax racket/base
33+
racket/list
34+
racket/syntax
35+
resyntax/private/more-syntax-parse-classes)
3336
racket/sequence
3437
rebellion/base/immutable-string
3538
rebellion/base/option
3639
rebellion/base/symbol
3740
rebellion/type/object
3841
resyntax/default-recommendations/private/definition-context
42+
resyntax/private/logger
3943
resyntax/private/source
4044
resyntax/private/syntax-neighbors
4145
resyntax/private/syntax-replacement
@@ -128,6 +132,8 @@
128132
pattern-directive:syntax-parse-pattern-directive ...
129133
replacement)
130134
#:declare description (expr/c #'string?)
135+
#:attr log-statement (and (not (empty? (attribute pattern-directive)))
136+
#'(log-resyntax-debug "~a: partial match" 'id))
131137
(define id
132138
(constructor:refactoring-rule
133139
#:name 'id
@@ -136,7 +142,9 @@
136142
(λ (stx)
137143
(syntax-parse stx
138144
(~@ . parse-option) ...
139-
[pattern (~@ . pattern-directive) ... (present #'replacement)]
145+
[pattern
146+
(~? (~@ #:do [log-statement]))
147+
(~@ . pattern-directive) ... (present #'replacement)]
140148
[_ absent])))))
141149

142150

@@ -154,12 +162,16 @@
154162
#:with body-matching-id (format-id #'macro-introduced-context "body-matching-~a" #'id)
155163
#:with expression-matching-id (format-id #'macro-introduced-context "expression-matching-~a" #'id)
156164

165+
#:attr log-statement (and (not (empty? (attribute pattern-directive)))
166+
#'(log-resyntax-debug "~a: partial match" 'id))
167+
157168
(begin
158169

159170
(define-splicing-syntax-class body-matching-id
160171
#:attributes ([refactored 1])
161172
(~@ . parse-option) ...
162173
(pattern splicing-pattern
174+
(~? (~@ #:do [log-statement]))
163175
(~@ . pattern-directive) ...
164176
#:with (refactored (... ...)) #'(splicing-replacement ...)))
165177

default-recommendations/for-loop-shortcuts-test.rkt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,43 @@ test: "named let loop over list can be replaced by for/list"
634634
------------------------------------------------------------
635635

636636

637+
test: "named let loop can be replaced with for/and when equivalent"
638+
------------------------------------------------------------
639+
(require racket/list)
640+
(define (f xs big? red?)
641+
(let loop ([xs xs])
642+
(cond
643+
[(empty? xs) #true]
644+
[(and (big? (first xs)) (not (red? (car xs))))
645+
(loop (rest xs))]
646+
[else #false])))
647+
------------------------------------------------------------
648+
------------------------------------------------------------
649+
(require racket/list)
650+
(define (f xs big? red?)
651+
(for/and ([x (in-list xs)])
652+
(and (big? x) (not (red? x)))))
653+
------------------------------------------------------------
654+
655+
656+
test: "named let loop can be replaced with for/or when equivalent"
657+
------------------------------------------------------------
658+
(require racket/list)
659+
(define (f xs big? red?)
660+
(let loop ([xs xs])
661+
(cond
662+
[(empty? xs) #false]
663+
[(and (big? (first xs)) (not (red? (car xs)))) #true]
664+
[else (loop (rest xs))])))
665+
------------------------------------------------------------
666+
------------------------------------------------------------
667+
(require racket/list)
668+
(define (f xs big? red?)
669+
(for/or ([x (in-list xs)])
670+
(and (big? x) (not (red? x)))))
671+
------------------------------------------------------------
672+
673+
637674
test: "append-map with for/list can be replaced by for*/list"
638675
------------------------------------------------------------
639676
(require racket/list)

default-recommendations/for-loop-shortcuts.rkt

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
resyntax/default-recommendations/private/boolean
1717
resyntax/default-recommendations/private/lambda-by-any-name
1818
resyntax/default-recommendations/private/let-binding
19+
resyntax/default-recommendations/private/list-function
1920
resyntax/default-recommendations/private/metafunction
2021
resyntax/default-recommendations/private/syntax-equivalence
2122
resyntax/default-recommendations/private/syntax-identifier-sets
2223
resyntax/default-recommendations/private/syntax-lines
2324
resyntax/private/identifier-naming
25+
resyntax/private/logger
2426
resyntax/private/syntax-traversal
2527
syntax/parse)
2628

@@ -376,35 +378,89 @@ return just that result."
376378
#:literals (let cond else null? empty? null quote car first cdr rest cons)
377379
(let loop:id ([vs:id init-list])
378380
(cond
379-
[((~or null? empty?) vs2:id) (~or null '())]
381+
[(:empty-predicate-by-any-name vs2:id) :empty-list-by-any-name]
380382
[else
381383
loop-body:expr ...
382-
(cons loop-element:expr
383-
(loop2:id ((~or cdr rest) vs3:id)))]))
384+
(cons loop-element:expr (loop2:id (:rest-by-any-name vs3:id)))]))
384385
#:when (free-identifier=? #'loop #'loop2)
385386
#:when (free-identifier=? #'vs #'vs2)
386387
#:when (free-identifier=? #'vs #'vs3)
387-
#:when (for*/and ([body-stx (in-list (cons #'loop-element (attribute loop-body)))]
388-
[vs-usage
389-
(syntax-search body-stx [(~var usage (expression-directly-enclosing #'vs))])]
390-
#:unless (syntax-free-identifier=? vs-usage #'(car vs)))
391-
(syntax-free-identifier=? vs-usage #'(first vs)))
388+
#:when (not
389+
(for/or ([body-stx (in-list (cons #'loop-element (attribute loop-body)))])
390+
(syntax-find-first body-stx
391+
(~and (~var usage (expression-directly-enclosing (attribute vs)))
392+
(~not (:first-by-any-name _))))))
392393
#:cut
393394

394395
#:with element-id (depluralize-id #'vs)
395396

396397
#:with (modified-result-element modified-body ...)
397398
(for/list ([body-stx (cons #'loop-element (attribute loop-body))])
398399
(syntax-traverse body-stx
399-
#:literals (car first)
400-
[(car vs-usage:id) #:when (free-identifier=? #'vs-usage #'vs) #'element-id]
401-
[(first vs-usage:id) #:when (free-identifier=? #'vs-usage #'vs) #'element-id]))
400+
[(:first-by-any-name vs-usage:id) #:when (free-identifier=? #'vs-usage #'vs) #'element-id]))
402401

403402
(for/list ([element-id (in-list init-list)])
404403
modified-body ...
405404
modified-result-element))
406405

407406

407+
(define-refactoring-rule named-let-loop-to-for/and
408+
#:description "This named `let` expression is equivalent to a `for/and` loop."
409+
#:literals (let cond else)
410+
(let loop:id ([vs:id init-list])
411+
(cond
412+
[(:empty-predicate-by-any-name vs2:id) #true]
413+
[element-condition:expr (loop2:id (:rest-by-any-name vs3:id))]
414+
[else #false]))
415+
416+
#:when (free-identifier=? (attribute loop) (attribute loop2))
417+
#:when (free-identifier=? (attribute vs) (attribute vs2))
418+
#:when (free-identifier=? (attribute vs) (attribute vs3))
419+
#:when (not (syntax-find-first (attribute element-condition)
420+
(~and (~var usage (expression-directly-enclosing (attribute vs)))
421+
(~not (:first-by-any-name _)))))
422+
#:cut
423+
424+
#:with element-id (depluralize-id (attribute vs))
425+
#:with modified-element-condition
426+
(syntax-traverse (attribute element-condition)
427+
[(:first-by-any-name vs-usage:id)
428+
#:when (free-identifier=? (attribute vs) (attribute vs-usage))
429+
(attribute element-id)])
430+
431+
(for/and ([element-id (in-list init-list)])
432+
modified-element-condition))
433+
434+
435+
(define-refactoring-rule named-let-loop-to-for/or
436+
#:description "This named `let` expression is equivalent to a `for/or` loop."
437+
#:literals (let cond else)
438+
(let loop:id ([vs:id init-list])
439+
(cond
440+
[(:empty-predicate-by-any-name vs2:id) #false]
441+
[element-condition:expr #true]
442+
[else (loop2:id (:rest-by-any-name vs3:id))]))
443+
444+
#:when (free-identifier=? (attribute loop) (attribute loop2))
445+
#:when (free-identifier=? (attribute vs) (attribute vs2))
446+
#:when (free-identifier=? (attribute vs) (attribute vs3))
447+
#:when (not (syntax-find-first (attribute element-condition)
448+
(~and (~var usage (expression-directly-enclosing (attribute vs)))
449+
(~not (:first-by-any-name _)))))
450+
#:cut
451+
452+
#:with element-id (depluralize-id (attribute vs))
453+
#:with modified-element-condition
454+
(syntax-traverse (attribute element-condition)
455+
[(:first-by-any-name vs-usage:id)
456+
#:when (free-identifier=? (attribute vs) (attribute vs-usage))
457+
(attribute element-id)])
458+
459+
(for/or ([element-id (in-list init-list)])
460+
modified-element-condition))
461+
462+
463+
408464
(define-refactoring-rule named-let-loop-to-for/first-in-vector
409465
#:description "This loop can be replaced by a simpler, equivalent `for/first` loop."
410466
#:literals (let add1 + vector-length vector-ref if and <)
@@ -466,8 +522,10 @@ return just that result."
466522
list->set-to-for/set
467523
list->vector-to-for/vector
468524
map-to-for
469-
named-let-loop-to-for/list
525+
named-let-loop-to-for/and
470526
named-let-loop-to-for/first-in-vector
527+
named-let-loop-to-for/list
528+
named-let-loop-to-for/or
471529
nested-for-to-for*
472530
nested-for/and-to-for*/and
473531
nested-for/or-to-for*/or
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#lang racket/base
2+
3+
4+
(provide empty-list-by-any-name
5+
empty-predicate-by-any-name
6+
first-by-any-name
7+
rest-by-any-name)
8+
9+
10+
(require racket/list
11+
syntax/parse)
12+
13+
14+
;@----------------------------------------------------------------------------------------------------
15+
16+
17+
(define-syntax-class empty-list-by-any-name
18+
#:literals (quote null empty list)
19+
(pattern (~or '() null empty (list))))
20+
21+
22+
(define-syntax-class empty-predicate-by-any-name
23+
#:literals (null? empty?)
24+
(pattern (~or null? empty?)))
25+
26+
27+
(define-syntax-class first-by-any-name
28+
#:literals (car first)
29+
(pattern (~or car first)))
30+
31+
32+
(define-syntax-class rest-by-any-name
33+
#:literals (cdr rest)
34+
(pattern (~or cdr rest)))

private/syntax-traversal.rkt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
#:when (for/or ([part-stx (in-list (attribute part))]
3939
#:when (identifier? part-stx))
4040
(free-identifier=? id part-stx)))
41-
(pattern (part ... . tail-part)
41+
(pattern (part ...+ . tail-part)
4242
#:when (for/or ([part-stx (in-list (cons #'tail-part (attribute part)))]
4343
#:when (identifier? part-stx))
4444
(free-identifier=? id part-stx))))
@@ -124,7 +124,7 @@
124124
(define-syntax-parse-rule
125125
(syntax-traverse (~var stx-expr (expr/c #'syntax?))
126126
option:syntax-parse-option ...
127-
[clause-pattern directive:syntax-parse-pattern-directive ... clause-body:expr ...] ...)
127+
[clause-pattern directive:syntax-parse-pattern-directive ... clause-body:expr ...+] ...)
128128
(let ()
129129
(define-syntax-class traversal-case
130130
#:attributes (traversed)

0 commit comments

Comments
 (0)