# Title

String Library (reduced)

# Author

John Cowan

# Status

This SRFI is currently in final status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to `srfi-152@nospamsrfi.schemers.org`. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.

- Received: 2017/6/2
- Draft #1 published: 2017/6/3
- Draft #2 published: 2017/6/9
- Draft #3 published: 2017/6/22
- Draft #4 published: 2017/6/25
- Draft #5 published: 2017/7/17
- Draft #6 published: 2017/9/11
- Draft #7 published: 2017/9/26
- Finalized: 2017/10/4

# Table of contents

# Abstract

Scheme has an impoverished set of string-processing utilities, which is a problem for authors of portable code. This SRFI proposes a coherent and comprehensive set of string-processing procedures. It is a reduced version of SRFI 13 that has been aligned with SRFI 135, Immutable Texts. Unlike SRFI 13, it has been made consistent with the R5RS, R6RS, and R7RS-small string procedures.

# Procedure Index

Here is a list of the procedures provided by this SRFI:

Predicates

      string?
      string-null?
      string-every string-any

Constructors

      make-string string
      string-tabulate
      string-unfold string-unfold-right

Conversion

      string->vector string->list
      vector->string list->string
      reverse-list->string

Selection

      string-length
      string-ref
      substring
      string-copy
      string-take string-take-right
      string-drop string-drop-right
      string-pad string-pad-right
      string-trim string-trim-right string-trim-both

Replacement

      string-replace

Comparison

      string=? string-ci=?
      string<? string-ci<?
      string>? string-ci>?
      string<=? string-ci<=?
      string>=? string-ci>=?

Prefixes and suffixes

      string-prefix-length string-suffix-length
      string-prefix? string-suffix?

Searching

      string-index string-index-right
      string-skip string-skip-right
      string-contains string-contains-right
      string-take-while string-take-while-right
      string-drop-while string-drop-while-right
      string-break string-span

Concatenation

      string-append string-concatenate string-concatenate-reverse
      string-join

Fold and map and friends

      string-fold string-fold-right
      string-map string-for-each
      string-count
      string-filter string-remove

Replication and splitting

      string-replicate
      string-segment string-split

Input-output

      read-string write-string

Mutation

      string-set! string-fill! string-copy!

# Rationale

This SRFI is based upon SRFI 130, copying much of its structure and wording, but eliminating the concept of cursors. However, it is textually derived from SRFI 135, in order to gain access to the editorial improvements made to the text of that SRFI, which was itself based on SRFI 130. Ultimately the origin of all these SRFIs is SRFI 13.

This SRFI omits the following bells, whistles, and gongs of SRFI 13:

- the Knuth-Morris-Pratt string search algorithm (still used internally by the sample implementation but not exposed)
- case-insensitive operations, other than the ones in R7RS-small
- titlecase operations
- direct comparison of substrings
- mutation procedures, other than the ones in R7RS-small
- string reversal (even more pointless in a Unicode age)
- characters and SRFI 14 character sets as alternatives to predicates

In addition, this SRFI includes the `string-segment` and `string-split` procedures from other sources. For completeness, `string-take-while`, `string-drop-while`, `string-take-while-right`, and `string-drop-while-right` are also provided.

There are no performance guarantees for any of the procedures in this SRFI.

The Scheme programming language does not expose the internal representation of strings. Some implementations of Scheme use UTF-32 or a similar encoding, which makes `string-length`, `string-ref`, and `string-set!` run in O(1) time. Some implementations use UTF-16 or UTF-8, which save space at the expense of making `string-ref` take time proportional to the length of a string. Others allow only 256 characters, typically the Latin-1 repertoire.

Although Scheme's string data type allows portable code to use strings independently of their internal representation, the variation in performance between implementations has created a problem for programs that use long strings. In some systems, long strings are inefficient with respect to space; in other systems, long strings are inefficient with respect to time. Consequently, this SRFI suggests that Scheme's mutable strings be used only for relatively short sequences of characters, while using the immutable texts defined by SRFI 135 for long sequences of characters.

# Specification

Procedures present in R5RS, R6RS, and R7RS-small are marked (R5RS). Procedures present in R5RS and R6RS but with additional arguments in R7RS-small are marked (R5RS+). Procedures present in R6RS and R7RS-small are marked (R6-R7RS). Procedures present in R6RS only are marked (R6RS). Procedures present in R7RS-small only are marked (R7RS-small).

Except as noted, the results returned from the procedures of this SRFI must be newly allocated strings. This is a change from the definition of SRFIs 13 and 130, though most Schemes do not support sharable strings in any case. However, the empty string need not be newly allocated.

The procedures of this SRFI follow a consistent naming scheme, and are consistent with the conventions developed in SRFI 1 and used in SRFI 13, SRFI 130, and SRFI 135. In particular, procedures that have left/right directional variants use no suffix to specify left-to-right operation, `-right` to specify right-to-left operation, and `-both` to specify both. One discrepancy between SRFI 1 and other SRFIs is in the tabulate procedure: SRFI 1's `list-tabulate` takes the length argument first, before the procedure, whereas all string SRFIs put the procedure first, in line with mapping and folding operations.

The order of common arguments is consistent across the different procedures. In particular, all procedures place the main string to be operated on first, with the exception of the mapping and folding procedures, which are consistent with R7RS-small and SRFI 1.

If a procedure's return value is said to be "unspecified," the procedure returns a single result whose value is unconstrained and might even vary from call to call.

## Notation

In the following procedure specifications:

- A string argument is a string.
- A char argument is a character.
- An idx argument is an exact non-negative integer specifying a valid character index into a string. The valid character indexes of a string string of length n are the exact integers idx satisfying 0 <= idx < n.
- A k argument or result is a position: an exact non-negative integer that is either a valid character index for one of the string arguments or is the length of a string argument.
- start and end arguments are positions specifying a half-open interval of indexes for a substring. When omitted, start defaults to 0 and end to the length of the corresponding string argument. It is an error unless 0 <= start <= end <= `(string-length string)`; the sample implementations detect that error and raise an exception.
- A len or nchars argument is an exact non-negative integer specifying some number of characters, usually the length of a string.
- A pred argument is a unary character predicate, taking a character as its one argument and returning a value that will be interpreted as true or false. Unless noted otherwise, as with `string-every` and `string-any`, all predicates passed to procedures specified in this SRFI may be called in any order and any number of times. It is an error if pred has side effects or does not behave functionally (returning the same result whenever it is called with the same character); the sample implementation does not detect those errors.
- An obj argument may be any value at all.

It is an error to pass values that violate the specification above.

Arguments given in square brackets are optional. Unless otherwise noted in the string describing the procedure, any prefix of these optional arguments may be supplied, from zero arguments to the full list. When a procedure returns multiple values, this is shown by listing the return values in square brackets as well. So, for example, the procedure with signature

    halts? f [x init-store] → [boolean integer]

would take one (f), two (f, x) or three (f, x, init-store) input arguments, and return two values, a boolean and an integer.

An argument followed by "`...`" means zero or more elements. So the procedure with the signature

    sum-squares x ...  → number

takes zero or more arguments (x ...), while the procedure with signature

    spell-check doc dict1 dict2 ... → string-list

takes two required arguments (doc and dict1) and zero or more optional arguments (dict2 ...).

## Procedures

### Predicates

`string?` obj → boolean (R5RS)
  Is obj a string?
`string-null?` string → boolean
  Is string the empty string?
`string-every` pred string [start end] → value
`string-any` pred string [start end] → value

  Checks to see if every/any character in string satisfies pred, proceeding from left (index start) to right (index end). These procedures are short-circuiting: if pred returns false, `string-every` does not call pred on subsequent characters; if pred returns true, `string-any` does not call pred on subsequent characters; Both procedures are "witness-generating":

  - If `string-every` is given an empty interval (with start = end), it returns `#t`.
  - If `string-every` returns true for a non-empty interval (with start < end), the returned true value is the one returned by the final call to the predicate on `(string-ref (string-copy string) (- end 1))`.
  - If `string-any` returns true, the returned true value is the one returned by the predicate.

  Note: The names of these procedures do not end with a question mark. This indicates a general value is returned instead of a simple boolean (`#t` or `#f`).

### Constructors

`make-string` len char → string (R5RS)
  Returns a string of the given length filled with the given character.
`string` char ... → string (R5RS)
  Returns a string consisting of the given characters.
`string-tabulate` proc len → string
  Proc is a procedure that accepts an exact integer as its argument and returns a character. Constructs a string of size len by calling proc on each value from 0 (inclusive) to len (exclusive) to produce the corresponding element of the string. The order in which proc is called on those indexes is not specified.

  Rationale: Although `string-unfold` is more general, `string-tabulate` is likely to run faster for the common special case it implements.

`string-unfold` stop? mapper successor seed [base make-final] → string
  This is a fundamental constructor for strings.
  - successor is used to generate a series of "seed" values from the initial seed:
    seed, (successor seed), (successor2 seed), (successor3 seed), ...
  - stop? tells us when to stop — when it returns true when applied to one of these seed values.
  - mapper maps each seed value to the corresponding character(s) in the result string, which are assembled into that string in left-to-right order. It is an error for mapper to return anything other than a character or string.
  - base is the optional initial/leftmost portion of the constructed string, which defaults to the empty string `""`. It is an error if base is anything other than a character or string.
  - make-final is applied to the terminal seed value (on which stop? returns true) to produce the final/rightmost portion of the constructed string. It defaults to `(lambda (x) "")`. It is an error for make-final to return anything other than a character or string.

  `string-unfold` is a fairly powerful string constructor. You can use it to convert a list to a string, read a port into a string, reverse a string, copy a string, and so forth. Examples:

      (port->string p) = (string-unfold eof-object?
                                 values
                                 (lambda (x) (read-char p))
                                 (read-char p))

      (list->string lis) = (string-unfold null? car cdr lis)

      (string-tabulate f size) = (string-unfold (lambda (i) (= i size)) f add1 0)

  To map f over a list lis, producing a string:

      (string-unfold null? (compose f car) cdr lis)

  Interested functional programmers may enjoy noting that `string-fold-right` and `string-unfold` are in some sense inverses. That is, given operations knull?, kar, kdr, and kons, and a value knil satisfying

      (kons (kar x) (kdr x)) = x  and  (knull? knil) = #t

  then

      (string-fold-right kons knil (string-unfold knull? kar kdr x)) = x

  and

      (string-unfold knull? kar kdr (string-fold-right kons knil string)) = string.

  This combinator pattern is sometimes called an "anamorphism."

  Note: Implementations should not allow the size of strings created by `string-unfold` to be limited by limits on stack size.

`string-unfold-right` stop? mapper successor seed [base make-final] → string
  This is a fundamental constructor for strings. It is the same as `string-unfold` except the results of mapper are assembled into the string in right-to-left order, base is the optional rightmost portion of the constructed string, and make-final produces the leftmost portion of the constructed string.

      (string-unfold-right (lambda (n) (< n (char->integer #\A)))
                         (lambda (n) (char-downcase (integer->char n)))
                         (lambda (n) (- n 1))
                         (char->integer #\Z)
                         #\space
                         (lambda (n) " The English alphabet: "))
          => " The English alphabet: abcdefghijklmnopqrstuvwxyz "

### Conversion

`string->vector` string [start end] → char-vector (R7RS-small)
`string->list` string [start end] → char-list (R5RS+)
  These procedures return a newly allocated (unless empty) vector or list of the characters that make up the given substring.
`vector->string` char-vector [start end] → string (R7RS-small)
`list->string` char-list → string (R5RS)
  These procedures return a string containing the characters of the given (sub)vector or list. The behavior of the string will not be affected by subsequent mutation of the given vector or list.
`reverse-list->string` char-list → string
  Semantically equivalent to `(compose list->string reverse)`:

      (reverse-list->string '(#\a #\B #\c)) → "cBa"

  This is a common idiom in the epilogue of string-processing loops that accumulate their result using a list in reverse order. (See also `string-concatenate-reverse` for the "chunked" variant.)

### Selection

`string-length` string → len (R5RS)
  Returns the number of characters within the given string.
`string-ref` string idx → char (R5RS)
  Returns character string[idx], using 0-origin indexing.
`substring` string start end → string (R5RS)
`string-copy` string [start end] → string (R5RS+)
  These procedures return a string containing the characters of string beginning with index start (inclusive) and ending with index end (exclusive). The only difference is that `substring` requires all three arguments, whereas `string-copy` requires only one.
`string-take` string nchars → string
`string-drop` string nchars → string
`string-take-right` string nchars → string
`string-drop-right` string nchars → string
  `string-take` returns a string containing the first nchars of string; `string-drop` returns a string containing all but the first nchars of string. `string-take-right` returns a string containing the last nchars of string; `string-drop-right` returns a string containing all but the last nchars of string.

      (string-take "Pete Szilagyi" 6) => "Pete S"
      (string-drop "Pete Szilagyi" 6) => "zilagyi"

      (string-take-right "Beta rules" 5) => "rules"
      (string-drop-right "Beta rules" 5) => "Beta "

  It is an error to take or drop more characters than are in the string:

      (string-take "foo" 37) => error

`string-pad` string len [char start end] → string
`string-pad-right` string len [char start end] → string
  Returns a string of length len comprised of the characters drawn from the given subrange of string, padded on the left (right) by as many occurrences of the character char as needed. If string has more than len chars, it is truncated on the left (right) to length len. char defaults to `#\space`.

      (string-pad     "325" 5) => "  325"
      (string-pad   "71325" 5) => "71325"
      (string-pad "8871325" 5) => "71325"

`string-trim` string [pred start end] → string
`string-trim-right` string [pred start end] → string
`string-trim-both` string [pred start end] → string
  Returns a string obtained from the given subrange of string by skipping over all characters on the left side / on the right side / on both sides that satisfy the second argument pred: pred defaults to `char-whitespace?`.

      (string-trim-both "  The outlook wasn't brilliant,  \n\r")
          => "The outlook wasn't brilliant,"

### Replacement

`string-replace` string1 string2 start1 end1 [start2 end2] → string
  Returns

      (string-append (substring string1 0 start1)
                      (substring string2 start2 end2)
                      (substring string1 end1 (string-length string1)))

  That is, the segment of characters in string1 from start1 to end1 is replaced by the segment of characters in string2 from start2 to end2. If start1=end1, this simply splices the characters drawn from string2 into string1 at that position.

  Examples:

      (string-replace "The TCL programmer endured daily ridicule."
                       "another miserable perl drone" 4 7 8 22)
          => "The miserable perl programmer endured daily ridicule."

      (string-replace "It's easy to code it up in Scheme." "lots of fun" 5 9)
          => "It's lots of fun to code it up in Scheme."

      (define (string-insert s i t) (string-replace s t i i))

      (string-insert "It's easy to code it up in Scheme." 5 "really ")
          => "It's really easy to code it up in Scheme."

      (define (string-set s i c) (string-replace s (string c) i (+ i 1)))

      (string-set "String-ref runs in O(n) time." 21 #\1)
          => "String-ref runs in O(1) time."

### Comparison

`string=?` string1 string2 string3 ... → boolean (R5RS)
  Returns `#t` if all the strings have the same length and contain exactly the same characters in the same positions; otherwise returns `#f`.
`string<?` string1 string2 string3 ... → boolean (R5RS)
`string>?` string1 string2 string3 ... → boolean (R5RS)
`string<=?` string1 string2 string3 ... → boolean (R5RS)
`string>=?` string1 string2 string3 ... → boolean (R5RS)
  These procedures return `#t` if their arguments are (respectively): monotonically increasing, monotonically decreasing, monotonically non-decreasing, or monotonically non-increasing.

  These comparison predicates are required to be transitive.

  These procedures compare strings in an implementation-defined way. One approach is to make them the lexicographic extensions to strings of the corresponding orderings on characters. In that case, `string<?` would be the lexicographic ordering on strings induced by the ordering `char<?` on characters, and if two strings differ in length but are the same up to the length of the shorter string, the shorter string would be considered to be lexicographically less than the longer string. However, implementations are also allowed to use more sophisticated locale-specific orderings.

  In all cases, a pair of strings must satisfy exactly one of `string<?`, `string=?`, and `string>?`, must satisfy `string<=?` if and only if they do not satisfy `string>?`, and must satisfy `string>=?` if and only if they do not satisfy `string<?`.

`string-ci=?` string1 string2 string3 ... → boolean (R5RS)
  Returns `#t` if, after calling `string-foldcase` on each of the arguments, all of the case-folded strings would have the same length and contain the same characters in the same positions; otherwise returns `#f`.
`string-ci<?` string1 string2 string3 ... → boolean (R5RS)
`string-ci>?` string1 string2 string3 ... → boolean (R5RS)
`string-ci<=?` string1 string2 string3 ... → boolean (R5RS)
`string-ci>=?` string1 string2 string3 ... → boolean (R5RS)
  These procedures behave as though they had called `string-foldcase` on their arguments before applying the corresponding procedures without "`-ci`".

### Prefixes and suffixes

`string-prefix-length` string1 string2 [start1 end1 start2 end2] → integer
`string-suffix-length` string1 string2 [start1 end1 start2 end2] → integer
  Return the length of the longest common prefix/suffix of string1 and string2. For prefixes, this is equivalent to their "mismatch index" (relative to the start indexes).

  The optional start/end indexes restrict the comparison to the indicated substrings of string1 and string2.

`string-prefix?` string1 string2 [start1 end1 start2 end2] → boolean
`string-suffix?` string1 string2 [start1 end1 start2 end2] → boolean
  Is string1 a prefix/suffix of string2?

  The optional start/end indexes restrict the comparison to the indicated substrings of string1 and string2.

### Searching

`string-index` string pred [start end] → idx-or-false
`string-index-right` string pred [start end] → idx-or-false
`string-skip` string pred [start end] → idx-or-false
`string-skip-right` string pred [start end] → idx-or-false
  `string-index` searches through the given substring from the left, returning the index of the leftmost character satisfying the predicate pred. `string-index-right` searches from the right, returning the index of the rightmost character satisfying the predicate pred. If no match is found, these procedures return `#f`.

  The start and end arguments specify the beginning and end of the search; the valid indexes relevant to the search include start but exclude end. Beware of "fencepost" errors: when searching right-to-left, the first index considered is `(- end 1)`, whereas when searching left-to-right, the first index considered is start. That is, the start/end indexes describe the same half-open interval [start,end) in these procedures that they do in all other procedures specified by this SRFI.

  The skip functions are similar, but use the complement of the criterion: they search for the first char that doesn't satisfy pred. To skip over initial whitespace, for example, say

      (substring string
                  (or (string-skip string char-whitespace?)
                      (string-length string))
                  (string-length string))

`string-contains` string1 string2 [start1 end1 start2 end2] → idx-or-false
`string-contains-right` string1 string2 [start1 end1 start2 end2] → idx-or-false
  Does the substring of string1 specified by start1 and end1 contain the sequence of characters given by the substring of string2 specified by start2 and end2?

  Returns `#f` if there is no match. If start2 = end2, `string-contains` returns start1 but `string-contains-right` returns end1. Otherwise returns the index in string1 for the first character of the first/last match; that index lies within the half-open interval [start1,end1), and the match lies entirely within the [start1,end1) range of string1.

      (string-contains "eek -- what a geek." "ee" 12 18) ; Searches "a geek"
          => 15

  Note: The names of these procedures do not end with a question mark. This indicates a useful value is returned when there is a match.

`string-take-while` string pred [start end] → string
`string-take-while-right` string pred [start end] → string
  Returns the longest initial prefix/suffix of the substring of string specified by start and end whose elements all satisfy the predicate pred. (Not SRFI 13 procedures.)
`string-drop-while` string pred [start end] → string
`string-drop-while-right` string pred [start end] → string
  Drops the longest initial prefix/suffix of the substring of string specified by start and end whose elements all satisfy the predicate pred, and returns the rest of the string.

  These are the same as `string-trim` and `string-trim-right`, but with a different order of arguments. (Not SRFI 13 procedures.)

`string-span` string pred [start end] → [string string]
`string-break` string pred [start end] → [string string]
  `String-span` splits the substring of string specified by start and end into the longest initial prefix whose elements all satisfy pred, and the remaining tail. `String-break` inverts the sense of the predicate: the tail commences with the first element of the input string that satisfies the predicate. (Not SRFI 13 procedures.)

  In other words: `span` finds the initial span of elements satisfying pred, and `break` breaks the string at the first element satisfying pred.

  `String-span` is equivalent to

      (values (string-take-while pred string)
              (string-drop-while pred string))

### Concatenation

`string-append` string ... → string (R5RS)
  Returns a string whose sequence of characters is the concatenation of the sequences of characters in the given arguments.
`string-concatenate` string-list → string
  Concatenates the elements of string-list together into a single string.

  Rationale: Some implementations of Scheme limit the number of arguments that may be passed to an n-ary procedure, so the `(apply string-append string-list)` idiom, which is otherwise equivalent to using this procedure, is not as portable.

`string-concatenate-reverse` string-list [final-string end] → string
  With no optional arguments, calling this procedure is equivalent to

      (string-concatenate (reverse string-list))

  If the optional argument final-string is specified, it is effectively consed onto the beginning of string-list before performing the `list-reverse` and `string-concatenate` operations.

  If the optional argument end is given, only the characters up to but not including end in final-string are added to the result, thus producing

      (string-concatenate
        (reverse (cons (substring final-string 0 end)
                       string-list)))

  For example:

      (string-concatenate-reverse '(" must be" "Hello, I") " going.XXXX" 7)
        => "Hello, I must be going."

  Rationale: This procedure is useful when constructing procedures that accumulate character data into lists of string buffers, and wish to convert the accumulated data into a single string when done. The optional end argument accommodates that use case by allowing the final buffer to be only partially full without having to copy it a second time, as `string-take` would require.

  Note that reversing a string simply reverses the sequence of code points it contains. Caution should be taken if a grapheme cluster is divided between two string arguments.

`string-join` string-list [delimiter grammar] → string
  This procedure is a simple unparser; it pastes strings together using the delimiter string.

  string-list is a list of strings. delimiter is a string. The grammar argument is a symbol that determines how the delimiter is used, and defaults to `'infix`. It is an error for grammar to be any symbol other than these four:

  - `'infix` means an infix or separator grammar: insert the delimiter between list elements. An empty list will produce an empty string.
  - `'strict-infix` means the same as `'infix` if the string-list is non-empty, but will signal an error if given an empty list. (This avoids an ambiguity shown in the examples below.)
  - `'suffix` means a suffix or terminator grammar: insert the delimiter after every list element.
  - `'prefix` means a prefix grammar: insert the delimiter before every list element.

  The delimiter is the string used to delimit elements; it defaults to a single space " ".

      (string-join '("foo" "bar" "baz"))
               => "foo bar baz"
      (string-join '("foo" "bar" "baz") "")
               => "foobarbaz"
      (string-join '("foo" "bar" "baz") ":")
               => "foo:bar:baz"
      (string-join '("foo" "bar" "baz") ":" 'suffix)
               => "foo:bar:baz:"

      ;; Infix grammar is ambiguous wrt empty list vs. empty string:
      (string-join '()   ":") => ""
      (string-join '("") ":") => ""

      ;; Suffix and prefix grammars are not:
      (string-join '()   ":" 'suffix)) => ""
      (string-join '("") ":" 'suffix)) => ":"

### Fold and map and friends

`string-fold` kons knil string [start end] → value
`string-fold-right` kons knil string [start end] → value
  These are the fundamental iterators for strings.

  The `string-fold` procedure maps the kons procedure across the given string from left to right:

      (... (kons string[2] (kons string[1] (kons string[0] knil))))

  In other words, `string-fold` obeys the (tail) recursion

        (string-fold kons knil string start end)
      = (string-fold kons (kons string[start] knil) start+1 end)

  The `string-fold-right` procedure maps kons across the given string from right to left:

      (kons string[0]
            (... (kons string[end-3]
                       (kons string[end-2]
                             (kons string[end-1]
                                   knil)))))

  obeying the (tail) recursion

        (string-fold-right kons knil string start end)
      = (string-fold-right kons (kons string[end-1] knil) start end-1)

  Examples:

      ;;; Convert a string to a list of chars.
      (string-fold-right cons '() string)

      ;;; Count the number of lower-case characters in a string.
      (string-fold (lambda (c count)
                      (if (char-lower-case? c)
                          (+ count 1)
                          count))
                    0
                    string)

  The `string-fold-right` combinator is sometimes called a "catamorphism."

`string-map` proc string1 string2 ... → string (R7RS-small)
  It is an error if proc does not accept as many arguments as the number of string arguments passed to `string-map`, does not accept characters as arguments, or returns a value that is not a character or string.

  The `string-map` procedure applies proc element-wise to the characters of the string arguments, converts each value returned by proc to a string, and returns the concatenation of those strings. If more than one string argument is given and not all have the same length, then `string-map` terminates when the shortest string argument runs out. The dynamic order in which proc is called on the characters of the string arguments is unspecified, as is the dynamic order in which the coercions are performed. If any strings returned by proc are mutated after they have been returned and before the call to `string-map` has returned, then `string-map` returns a string with unspecified contents; the `string-map` procedure itself does not mutate those strings.

  Example:

      (string-map (lambda (c0 c1 c2)
                     (case c0
                      ((#\1) c1)
                      ((#\2) (string c2))
                      ((#\-) (string #\- c1))))
                   "1222-1111-2222"
                   "Hi There!"
                   "Dear John")
           => "Hear-here!"

`string-for-each` proc string1 string2 ... → unspecified (R7RS-small)
  It is an error if proc does not accept as many arguments as the number of string arguments passed to `string-map` or does not accept characters as arguments.

  The `string-for-each` procedure applies proc element-wise to the characters of the string arguments, going from left to right. If more than one string argument is given and not all have the same length, then `string-for-each` terminates when the shortest string argument runs out.

`string-count` string pred [start end] → integer
  Returns a count of the number of characters in the specified substring of string that satisfy the given predicate.
`string-filter` pred string [start end] → string
`string-remove` pred string [start end] → string
  Filter the given substring of string, retaining only those characters that satisfy / do not satisfy pred.

  Compatibility note: In SRFI 13, `string-remove` is called `string-delete`. This is inconsistent with SRFI 1 and other SRFIs.

### Replication and splitting

`string-replicate` string from to [start end] → string
  This is an "extended substring" procedure that implements replicated copying of a substring.

  string is a string; start and end are optional arguments that specify a substring of string, defaulting to 0 and the length of string. This substring is conceptually replicated both up and down the index space, in both the positive and negative directions. For example, if string is `"abcdefg"`, start is 3, and end is 6, then we have the conceptual bidirectionally-infinite string

          ...  d  e  f  d  e  f  d  e  f  d  e  f  d  e  f  d  e  f  d ...
              -9 -8 -7 -6 -5 -4 -3 -2 -1  0 +1 +2 +3 +4 +5 +6 +7 +8 +9

  `string-replicate` returns the substring of this string beginning at index from, and ending at to. It is an error if from is greater than to.

  You can use `string-replicate` to perform a variety of tasks:

  - To rotate a string left: `(string-replicate "abcdef" 2 8)` => `"cdefab"`
  - To rotate a string right: `(string-replicate "abcdef" -2 4)` => `"efabcd"`
  - To replicate a string: `(string-replicate "abc" 0 7)` => `"abcabca"`

  Note that

  - The from/to arguments give a half-open range containing the characters from index from up to, but not including, index to.
  - The from/to indexes are not expressed in the index space of string. They refer instead to the replicated index space of the substring defined by string, start, and end.

  It is an error if start=end, unless from=to, which is allowed as a special case.

  Compatibility note: In SRFI 13, this procedure is called `xsubstring`.

`string-segment` string k → list
  Returns a list of strings representing the consecutive substrings of length k. The last string may be shorter than k. (Not a SRFI 13 procedure.)
`string-split` string delimiter [grammar limit start end] → list
  Returns a list of strings representing the words contained in the substring of string from start (inclusive) to end (exclusive). The delimiter is a string to be used as the word separator. This will often be a single character, but multiple characters are allowed for use cases such as splitting on `"\r\n"`. The returned list will have one more item than the number of non-overlapping occurrences of the delimiter in the string. If delimiter is an empty string, then the returned list contains a list of strings, each of which contains a single character. (Not a SRFI 13 procedure; replaces `string-tokenize`).

  The grammar is a symbol with the same meaning as in the `string-join` procedure. If it is `infix`, which is the default, processing is done as described above, except an empty string produces the empty list; if grammar is `strict-infix`, then an empty string signals an error. The values `prefix` and `suffix` cause a leading/trailing empty string in the result to be suppressed.

  If limit is a non-negative exact integer, at most that many splits occur, and the remainder of string is returned as the final element of the list (so the result will have at most limit+1 elements). If limit is not specified or is `#f`, then as many splits as possible are made. It is an error if limit is any other value.

  To split on a regular expression, use SRFI 115's `regexp-split` procedure.

### Input-output

`read-string` k [port] → string (R7RS-small)
  Reads the next k characters, or as many as are available before the end of file, from the textual input port into a newly allocated string in left-to-right order and returns the string. If no characters are available before the end of file, an end-of-file object is returned. The default port is the value of `(current-input-port)`.
`write-string` string [port start end]→ unspecified (R7RS-small)
  Writes the characters of string from index start to index end onto textual output port port. The default port is the value of `(current-output-port)`.

### Mutation

`string-set!` string k char → unspecified (R5RS)
  The string-set! procedure stores char in element k of string.
`string-fill!` string fill [start end] → unspecified (R5RS+)
  The string-fill! procedure stores fill in elements start through end of string.
`string-copy!` to at from [start end] → unspecified (R7RS-small)
  Copies the characters of string from between start and end to string to, starting at at. The order in which characters are copied is unspecified, except that if the source and destination overlap, copying takes place as if the source is first copied into a temporary string and then into the destination. This can be achieved without allocating storage by making sure to copy in the correct direction in such circumstances.

# Sample implementation

The sample implementations of this SRFI are in the SRFI repository. The main implementation is portable but inefficient; since efficiency is not a design goal (use texts for that!), it should be satisfactory.

There are two modules for Chicken. One works on Chicken's native 8-bit strings; the other leverages the `utf8` egg to provide a UTF-8 facade over those same strings. This means that there is no reliable way to tell by inspection whether a string is 8-bit or UTF-8, and one must take precautions to avoid mixing them.

The Chicken modules `srfi-13 utf8 utf8-srfi-13 utf8-case-map` shouldn't be imported together into the same module or program with either `srfi-152` or `utf8-srfi-152`, as they are inherently incompatible. However, it is possible to import `utf8-srfi-152` and then cherry-pick non-conflicting identifiers from `utf8` with `(import (only utf8 read-char write-char print ...))`. There is no problem with the `utf8-srfi-14` and `unicode-char-sets` modules.

When importing any of the `scheme chicken data-structures extras` modules along with `utf8-srfi-152`, be sure to do it as follows to avoid conflicts:

    (import (except scheme
        make-string string string-length string-ref string-set! substring
        string->list list->string string-fill!))
    (import (except chicken
        reverse-list->string))
    (import (except data-structures
        string-split substring-index))
    (import (except extras
        read-string write-string))

When using the `srfi-152` module instead, import the `scheme` module as follows:

    (import (except scheme
      string->list string-fill!))

The other modules, if imported, must be restricted in the same way as shown above.

The R7RS library assumes the presence of all R7RS-small procedures and does not require excluding any of them, as this SRFI is inherently compatible with R7RS-small.

# Acknowledgements

I acknowledge the participants in the SRFI 152 mailing list, and everyone acknowledged in SRFI 135 (which acknowledges everyone acknowledged in SRFI 130 (which acknowledges everyone acknowledged in SRFI 13)). Particularly important are Olin Shivers, the author of SRFI 13, and Will Clinger, the author of SRFI 135.

As Olin said, we should not assume any of those individuals endorse this SRFI.

# Copyright

Copyright (C) John Cowan (2017).

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.