|
93 | 93 |
|
94 | 94 | (define (string-replacement #:start start #:end end #:contents contents) |
95 | 95 | (define content-list |
96 | | - (transduce contents (filtering (λ (r) (positive? (replacement-string-span r)))) #:into into-list)) |
| 96 | + (for/fold ([accumulated '()] |
| 97 | + [previous #false] |
| 98 | + #:result |
| 99 | + (reverse (append (if previous (list previous) (list)) accumulated))) |
| 100 | + ([piece contents] |
| 101 | + #:when (positive? (replacement-string-span piece))) |
| 102 | + (match (list previous piece) |
| 103 | + [(list #false _) (values accumulated piece)] |
| 104 | + [(list (inserted-string s1) (inserted-string s2)) |
| 105 | + (values accumulated (inserted-string (string-append s1 s2)))] |
| 106 | + [(list _ _) (values (cons previous accumulated) piece)]))) |
97 | 107 | (define new-span (transduce content-list (mapping replacement-string-span) #:into into-sum)) |
98 | 108 | (define max-end |
99 | 109 | (transduce content-list |
|
110 | 120 | #:contents content-list)) |
111 | 121 |
|
112 | 122 |
|
| 123 | +(module+ test |
| 124 | + (test-case "string-replacement constructor" |
| 125 | + |
| 126 | + (test-case "should merge insertions" |
| 127 | + (define initial-pieces |
| 128 | + (list (inserted-string "aaa") (inserted-string "bbb") (inserted-string "ccc"))) |
| 129 | + (define expected-pieces (list (inserted-string "aaabbbccc"))) |
| 130 | + (define replacement |
| 131 | + (string-replacement |
| 132 | + #:start 0 |
| 133 | + #:end 10 |
| 134 | + #:contents initial-pieces)) |
| 135 | + (check-equal? (string-replacement-contents replacement) expected-pieces)) |
| 136 | + |
| 137 | + (test-case "should not merge copied pieces" |
| 138 | + (define initial-pieces |
| 139 | + (list (copied-string 2 5) (copied-string 5 7) (copied-string 7 9))) |
| 140 | + (define replacement |
| 141 | + (string-replacement |
| 142 | + #:start 0 |
| 143 | + #:end 10 |
| 144 | + #:contents initial-pieces)) |
| 145 | + (check-equal? (string-replacement-contents replacement) initial-pieces)) |
| 146 | + |
| 147 | + (test-case "should not merge inserted pieces with copied pieces" |
| 148 | + (define initial-pieces |
| 149 | + (list (inserted-string "aaa") (copied-string 5 7) (inserted-string "bbb"))) |
| 150 | + (define replacement |
| 151 | + (string-replacement |
| 152 | + #:start 0 |
| 153 | + #:end 10 |
| 154 | + #:contents initial-pieces)) |
| 155 | + (check-equal? (string-replacement-contents replacement) initial-pieces)) |
| 156 | + |
| 157 | + (test-case "should merge inserted pieces when before copied piece" |
| 158 | + (define initial-pieces |
| 159 | + (list (inserted-string "aaa") (inserted-string "bbb") (copied-string 5 7))) |
| 160 | + (define expected-pieces (list (inserted-string "aaabbb") (copied-string 5 7))) |
| 161 | + (define replacement |
| 162 | + (string-replacement |
| 163 | + #:start 0 |
| 164 | + #:end 10 |
| 165 | + #:contents initial-pieces)) |
| 166 | + (check-equal? (string-replacement-contents replacement) expected-pieces)) |
| 167 | + |
| 168 | + (test-case "should merge inserted pieces when after copied piece" |
| 169 | + (define initial-pieces |
| 170 | + (list (copied-string 5 7) (inserted-string "ccc") (inserted-string "ddd"))) |
| 171 | + (define expected-pieces (list (copied-string 5 7) (inserted-string "cccddd"))) |
| 172 | + (define replacement |
| 173 | + (string-replacement |
| 174 | + #:start 0 |
| 175 | + #:end 10 |
| 176 | + #:contents initial-pieces)) |
| 177 | + (check-equal? (string-replacement-contents replacement) expected-pieces)))) |
| 178 | + |
| 179 | + |
113 | 180 | (define (string-replacement-length-change replacement) |
114 | 181 | (- (string-replacement-new-span replacement) (string-replacement-original-span replacement))) |
115 | 182 |
|
|
250 | 317 | (define/guard (string-replacement-normalize replacement original-string |
251 | 318 | #:preserve-start [preserve-start #false] |
252 | 319 | #:preserve-end [preserve-end #false]) |
| 320 | + (define left-normalized |
| 321 | + (string-replacement-left-normalize replacement original-string #:preserve-start preserve-start)) |
| 322 | + (define left-reversed (string-replacement-reverse left-normalized original-string)) |
| 323 | + (define reversed-original-string (string-reverse original-string)) |
| 324 | + (define reversed |
| 325 | + (string-replacement-left-normalize |
| 326 | + left-reversed |
| 327 | + reversed-original-string |
| 328 | + #:preserve-start (and preserve-end (- (string-length original-string) preserve-end)))) |
| 329 | + (string-replacement-reverse reversed reversed-original-string)) |
| 330 | + |
| 331 | + |
| 332 | +(define/guard (string-replacement-left-normalize replacement original-string |
| 333 | + #:preserve-start [preserve-start #false]) |
253 | 334 | (define replaced (string-apply-replacement original-string replacement)) |
254 | 335 | (guard (not (equal? original-string replaced)) #:else replacement) |
255 | 336 | (define actual-start |
256 | | - (inexact->exact (min (or preserve-start +inf.0) (string-diff-start original-string replaced)))) |
257 | | - (define actual-end |
258 | 337 | (inexact->exact |
259 | | - (max actual-start (or preserve-end -inf.0) (string-diff-end original-string replaced)))) |
| 338 | + (min (or preserve-start +inf.0) |
| 339 | + (string-diff-start original-string replaced) |
| 340 | + (string-replacement-original-end replacement)))) |
260 | 341 | (define left-trimmed-pieces |
261 | 342 | (let loop ([pieces (string-replacement-contents replacement)] |
262 | 343 | [pos (string-replacement-start replacement)]) |
|
267 | 348 | (if (< (+ pos piece-span) actual-start) |
268 | 349 | (loop remaining (+ pos piece-span)) |
269 | 350 | (cons (replacement-string-drop-left next-piece (- actual-start pos)) remaining))))) |
270 | | - (define right-trimmed-pieces |
271 | | - (let loop ([pieces (reverse left-trimmed-pieces)] |
272 | | - [pos (string-replacement-original-end replacement)]) |
273 | | - (guarded-block |
274 | | - (guard (> pos actual-end) #:else (reverse pieces)) |
275 | | - (guard-match (cons next-piece remaining) pieces #:else (list)) |
276 | | - (define piece-span (replacement-string-span next-piece)) |
277 | | - (if (> (- pos piece-span) actual-start) |
278 | | - (loop remaining (- pos piece-span)) |
279 | | - (reverse |
280 | | - (cons (replacement-string-drop-right next-piece (- pos actual-start)) remaining)))))) |
281 | 351 | (string-replacement #:start actual-start |
282 | | - #:end actual-end |
283 | | - #:contents right-trimmed-pieces)) |
| 352 | + #:end (string-replacement-original-end replacement) |
| 353 | + #:contents left-trimmed-pieces)) |
| 354 | + |
| 355 | + |
| 356 | +(define (string-replacement-reverse replacement original-string) |
| 357 | + (define new-start (- (string-length original-string) (string-replacement-original-end replacement))) |
| 358 | + (define new-end (+ new-start (string-replacement-original-span replacement))) |
| 359 | + (define new-contents |
| 360 | + (reverse |
| 361 | + (for/list ([piece (in-list (string-replacement-contents replacement))]) |
| 362 | + (match piece |
| 363 | + [(inserted-string s) (inserted-string (string-reverse s))] |
| 364 | + [(copied-string start end) |
| 365 | + (define new-start (- (string-length original-string) end)) |
| 366 | + (define new-end (+ new-start (- end start))) |
| 367 | + (copied-string new-start new-end)])))) |
| 368 | + (string-replacement #:start new-start #:end new-end #:contents new-contents)) |
| 369 | + |
| 370 | + |
| 371 | +(define (string-reverse s) |
| 372 | + (list->string (reverse (string->list s)))) |
284 | 373 |
|
285 | 374 |
|
286 | 375 | (define (string-diff-start original new) |
|
414 | 503 | (check-equal? (string-replacement-normalize replacement s) |
415 | 504 | (string-replacement #:start 13 |
416 | 505 | #:end 13 |
417 | | - #:contents (list (inserted-string "friend "))))))) |
| 506 | + #:contents (list (inserted-string "friend "))))) |
| 507 | + |
| 508 | + (test-case "normalizing by left-trimming a single size-increasing insertion" |
| 509 | + (define s "hello my big friend") |
| 510 | + (define s2 "hello my little friend") |
| 511 | + (define replacement-pieces (list (inserted-string "my little"))) |
| 512 | + (define replacement (string-replacement #:start 6 #:end 12 #:contents replacement-pieces)) |
| 513 | + (check-equal? (string-apply-replacement s replacement) s2) |
| 514 | + |
| 515 | + (define normalized (string-replacement-normalize replacement s)) |
| 516 | + |
| 517 | + (define expected |
| 518 | + (string-replacement #:start 9 #:end 12 #:contents (list (inserted-string "little")))) |
| 519 | + (check-equal? (string-apply-replacement s expected) s2) |
| 520 | + (check-equal? normalized expected)) |
| 521 | + |
| 522 | + (test-case "normalizing by left-trimming a single size-decreasing insertion" |
| 523 | + (define s "hello my little friend") |
| 524 | + (define s2 "hello my big friend") |
| 525 | + (define replacement-pieces (list (inserted-string "my big"))) |
| 526 | + (define replacement (string-replacement #:start 6 #:end 15 #:contents replacement-pieces)) |
| 527 | + (check-equal? (string-apply-replacement s replacement) s2) |
| 528 | + |
| 529 | + (define normalized (string-replacement-normalize replacement s)) |
| 530 | + |
| 531 | + (define expected |
| 532 | + (string-replacement #:start 9 #:end 15 #:contents (list (inserted-string "big")))) |
| 533 | + (check-equal? (string-apply-replacement s expected) s2) |
| 534 | + (check-equal? normalized expected)) |
| 535 | + |
| 536 | + (test-case "normalizing by right-trimming a single size-increasing insertion" |
| 537 | + (define s "hello my big friend") |
| 538 | + (define s2 "hello my little friend") |
| 539 | + (define replacement-pieces (list (inserted-string "little friend"))) |
| 540 | + (define replacement (string-replacement #:start 9 #:end 19 #:contents replacement-pieces)) |
| 541 | + (check-equal? (string-apply-replacement s replacement) s2) |
| 542 | + |
| 543 | + (define normalized (string-replacement-normalize replacement s)) |
| 544 | + |
| 545 | + (define expected |
| 546 | + (string-replacement #:start 9 #:end 12 #:contents (list (inserted-string "little")))) |
| 547 | + (check-equal? (string-apply-replacement s expected) s2) |
| 548 | + (check-equal? normalized expected)) |
| 549 | + |
| 550 | + (test-case "normalizing by right-trimming a single size-decreasing insertion" |
| 551 | + (define s "hello my little friend") |
| 552 | + (define s2 "hello my big friend") |
| 553 | + (define replacement-pieces (list (inserted-string "big friend"))) |
| 554 | + (define replacement (string-replacement #:start 9 #:end 22 #:contents replacement-pieces)) |
| 555 | + (check-equal? (string-apply-replacement s replacement) s2) |
| 556 | + |
| 557 | + (define normalized (string-replacement-normalize replacement s)) |
| 558 | + |
| 559 | + (define expected |
| 560 | + (string-replacement #:start 9 #:end 15 #:contents (list (inserted-string "big")))) |
| 561 | + (check-equal? (string-apply-replacement s expected) s2) |
| 562 | + (check-equal? normalized expected)))) |
418 | 563 |
|
419 | 564 |
|
420 | 565 | (define/guard (string-replacement-union replacement1 replacement2) |
|
0 commit comments