diff --git a/unstable/lens/main.rkt b/unstable/lens/main.rkt index e79e14f..6f516b7 100644 --- a/unstable/lens/main.rkt +++ b/unstable/lens/main.rkt @@ -6,6 +6,7 @@ "arrow.rkt" "isomorphism.rkt" "mapper.rkt" + "string-split.rkt" ) (provide (all-from-out "syntax.rkt" @@ -14,4 +15,5 @@ "arrow.rkt" "isomorphism.rkt" "mapper.rkt" + "string-split.rkt" )) diff --git a/unstable/lens/main.scrbl b/unstable/lens/main.scrbl index bf07f06..78219d2 100644 --- a/unstable/lens/main.scrbl +++ b/unstable/lens/main.scrbl @@ -15,3 +15,4 @@ this library being backwards-compatible. @include-section["arrow.scrbl"] @include-section["isomorphism.scrbl"] @include-section["mapper.scrbl"] +@include-section["string-split.scrbl"] diff --git a/unstable/lens/string-split.rkt b/unstable/lens/string-split.rkt new file mode 100644 index 0000000..5bb84d6 --- /dev/null +++ b/unstable/lens/string-split.rkt @@ -0,0 +1,56 @@ +#lang racket/base + +(require racket/contract/base) +(provide (contract-out + [string-split-lens + (-> (or/c immutable-string? char? regexp?) + (lens/c immutable-string? (listof immutable-string?)))] + )) + +(require racket/match + racket/string + lens/base/main + lens/util/immutable + ) +(module+ test + (require rackunit)) + +(define (string-split-lens sep) + (define sep-rx + (cond + [(string? sep) (regexp (regexp-quote sep))] + [(char? sep) (regexp (regexp-quote (string sep)))] + [(regexp? sep) sep] + [else (error 'bad)])) + (define (get str) + (map string->immutable-string (regexp-split sep-rx str))) + (define (set str lst) + (for ([s (in-list lst)]) + (when (regexp-match? sep-rx s) ; this would violate the lens laws + (error 'string-split-lens "expected a string not matching ~v, given: ~v" sep s))) + (define seps (regexp-match* sep-rx str)) + (match-define (cons fst rst) lst) + (string->immutable-string (string-append* fst (map string-append seps rst)))) + (make-lens get set)) + +(module+ test + (define ws-lens (string-split-lens #px"\\s+")) + (check-equal? (lens-view ws-lens " foo bar baz \r\n\t") + '("" "foo" "bar" "baz" "")) + (check-equal? (lens-set ws-lens " foo bar baz \r\n\t" '("a" "b" "c" "d" "e")) + "a b c d \r\n\te") + (check-equal? (lens-view ws-lens "a b c d \r\n\te") + '("a" "b" "c" "d" "e")) + (check-equal? (lens-set ws-lens "a b c d \r\n\te" '("" "foo" "bar" "baz" "")) + " foo bar baz \r\n\t") + (define newline-lens (string-split-lens "\n")) + (check-equal? (lens-view newline-lens "a,b\nc,d\ne,f,g") + '("a,b" "c,d" "e,f,g")) + (check-equal? (lens-set newline-lens "a,b\nc,d\ne,f,g" '("1" "2" "3")) + "1\n2\n3") + (define comma-lens (string-split-lens #\,)) + (check-equal? (lens-view comma-lens "a,b,c") + '("a" "b" "c")) + (check-equal? (lens-set comma-lens "a,b,c" '("1" "2" "3")) + "1,2,3") + ) diff --git a/unstable/lens/string-split.scrbl b/unstable/lens/string-split.scrbl new file mode 100644 index 0000000..40f613f --- /dev/null +++ b/unstable/lens/string-split.scrbl @@ -0,0 +1,26 @@ +#lang scribble/manual + +@(require lens/doc-util/main) + +@title{Splitting Strings} + +@defmodule[unstable/lens/string-split] + +@defproc[(string-split-lens [sep (or/c string? char? regexp?)]) lens?]{ +Creates a lens that splits a string into multiple pieces like +@racket[regexp-split] or @racket[string-split]. +@lenses-unstable-examples[ + (lens-view (string-split-lens ",") "a,b,c") + (lens-set (string-split-lens ",") "a,b,c" '("1" "2" "3")) +] +Lenses created by @racket[string-split-lens] do not trim strings first, so that +when viewing a target that either starts or ends with something matching +@racket[sep], the view will include empty strings as the first or last element, +which is consistant with @racket[regexp-split] or @racket[string-split] with +@racket[#:trim? #f]. This is also more useful when using @racket[lens-set]. +@lenses-unstable-examples[ + (lens-view (string-split-lens ",") ",b,c") + (lens-set (string-split-lens ",") ",b,c" '("a" "b" "c")) + (lens-view (string-split-lens ",") "a,b,c,") + (lens-set (string-split-lens ",") "a,b,c," '("a" "b" "c" "d")) +]}