scsh-0.5/scsh/lib/strings.txt

579 lines
25 KiB
Plaintext
Raw Normal View History

Todo:
parse-start+end parse-final-start+end need "string" in the name
Also, export macro binder.
What's up w/quotient? (quotient -1 3) = 0.
regexp-foldl
type regexp interface
land*
Let-optional:
A let-optional that parses a prefix of the args.
Arg checking forms that get used if it parses, but are not
applied to the default.
The Scheme Underground string library includes a rich set of operations
for manipulating strings. These are frequently useful for scripting and
other text-manipulation applications.
The library's design was influenced by the string libraries found in MIT
Scheme, Gambit, RScheme, MzScheme, slib, Common Lisp, Bigloo, guile, APL and
the SML standard basis. Some of the code bears a distant family relation to
the MIT Scheme implementation, and being derived from that code, is covered by
the MIT Scheme copyright (which is a fairly generic "free" copyright -- see
the source file for details). The fast KMP string-search code used in
SUBSTRING? was loosely adapted from old slib code by Stephen Bevan.
The library has the following design principles:
- *All* procedures involving character comparison are available in
both case-sensitive and case-insensitive forms.
- *All* functionality is available in substring and full-string forms.
- The procedures are spec'd so as to permit efficient implementation in a
Scheme that provided shared-text substrings (e.g., guile). This means that
you should not rely on many of the substring-selecting procedures to return
freshly-allocated strings. Careful attention is paid to the issue of which
procedures allocate fresh storage, and which are permitted to return results
that share storage with the arguments.
- Common Lisp theft:
+ inequality functions return mismatch index.
I generalised this so that this "protocol" is extended even to
the equality functions. This means that clients can be handed any generic
string-comparison function and rely on the meaning of the true value.
+ Common Lisp capitalisation definition
The library addresses some problems with the R5RS string procedures:
- Question marks after string-comparison functions (string=?, etc.)
This is inconsistent with numeric comparison functions, and ugly, too.
- String-comparison functions do not provide useful true value.
- STRING-COPY should have optional start/end args;
SUBSTRING shouldn't specify if it copies or returns shared bits.
- STRING-FILL! and STRING->LIST should take optional start/end args.
- No <> function provided.
In the following procedure specifications:
- Any S parameter is a string;
- START and END parameters are half-open string indices specifying
a substring within a string parameter; when optional, they default
to 0 and the length of the string, respectively. When specified, it
must be the case that 0 <= START <= END <= (string-length S), for
the corresponding parameter S. They typically restrict a procedure's
action to the indicated substring.
- A CHAR/CHAR-SET/PRED parameter is a value used to select/search
for a character in a string. If it is a character, it is used in
an equality test; if it is a character set, it is used as a
membership test; if it is a procedure, it is applied to the
characters as a test predicate.
This library contains a large number of procedures, but they follow
a consistent naming scheme. The names are composed of smaller lexemes
in a regular way that exposes the structure and relationships between the
procedures. This should help the programmer to recall or reconstitute the name
of the particular procedure that he needs when writing his own code. In
particular
- Procedures whose names end in "-ci" are case-insensitive variants.
- Procedures whose names end in "!" are side-effecting variants.
These procedures generally return an unspecified value.
- The order of common parameters is fairly consistent across the
different procedures.
For more text-manipulation functionality, see also the regular expression,
file-name, character set, and character->character partial map packages.
-------------------------------------------------------------------------------
* R4RS/R5RS procedures
The R4RS and R5RS reports define 22 string procedures. The string-lib
package includes 8 of these exactly as defined, 4 in an extended,
backwards-compatible way, and drops the remaining 10 (whose functionality
is available via other bindings).
The 8 procedures provided exactly as documented in the reports are
string?
make-string
string
string-length
string-ref
string-set!
string-append
list->string
The ten functions not included are the R4RS string-comparison functions:
string=? string-ci=?
string<? string-ci<?
string>? string-ci>?
string<=? string-ci<=?
string>=? string-ci>=?
The string-lib package provides alternate bindings.
Additionally, the four extended procedures are
string-fill! s char [start end] -> unspecific
string->list s [start end] -> char-list
substring s start [end] -> string
string-copy s [start end] -> string
These procedures are documented in the following section. In brief, they are
extended to take optional start/end parameters specifying substring ranges;
Additionally, SUBSTRING is allowed to return a value that shares storage with
its argument.
* Procedures
These procedures are contained in the Scheme 48 package "string-lib",
which is open in the default user package. They are not found in the
"scsh" package; script writers and other programmers that use the Scheme
48 module system must open string-lib explicitly.
string-map proc s [start end] -> string
string-map! proc s [start end] -> unspecified
PROC is a char->char procedure; it is mapped over S.
Note: no sequence order is specified.
string-fold kons knil s [start end] -> value
string-fold-right kons knil s [start end] -> value
These are the fundamental iterators for strings.
The left-fold operator maps the KONS procedure across the
string from left to right
(... (kons s[2] (kons s[1] (kons s[0] knil))))
In other words, string-fold obeys the recursion
(string-fold kons knil s start end) =
(string-fold kons (kons s[start] knil) start+1 end)
The right-fold operator maps the KONS procedure across the
string from right to left
(kons s[0] (... (kons s[end-3] (kons s[end-2] (kons s[end-1] knil)))))
obeying the recursion
(string-fold-right kons knil s start end) =
(string-fold-right kons (kons s[end-1] knil) start end-1)
Examples:
To convert a string to a list of chars:
(string-fold-right cons '() s)
To count the number of lower-case characters in a string:
(string-fold (lambda (c count)
(if (char-set-contains? char-set:lower c)
(+ count 1)
count))
0
s)
string-unfold p f g seed -> string
This is the fundamental constructor for strings.
- G is used to generate a series of "seed" values from the initial seed:
SEED, (G SEED), (G^2 SEED), (G^3 SEED), ...
- P tells us when to stop -- when it returns true when applied to one
of these seed values.
- F maps each seed value to the corresponding character
in the result string.
More precisely, the following (simple, inefficient) definition holds:
(define (string-unfold p f g seed)
(if (p seed) ""
(string-append (string (f seed))
(string-unfold p f g (g seed)))))
STRING-UNFOLD is a fairly powerful constructor -- you can use it to
reverse a string, copy a string, convert a list to a string, read
a port into 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)
(tabulate-string 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)
string-tabulate proc len -> string
PROC is an integer->char procedure. Construct a string of size LEN
by applying PROC to each index to produce the corresponding string
element. The order in which PROC is applied to the indices is not
specified.
string-for-each proc s [start end] -> unspecified
string-iter proc s [start end] -> unspecified
Apply PROC to each character in S.
STRING-FOR-EACH has no specified iteration order.
STRING-ITER is required to iterate from START to END
in increasing order.
string-every? pred s [start end] -> boolean
string-any? pred s [start end] -> value
Note: no sequence order specified.
Checks to see if predicate PRED is true of every / any character in S.
STRING-ANY? is witness-generating -- it applies PRED to the elements
of S, returning the first true value it finds, otherwise false.
string-compare s1 s2 lt-proc eq-proc gt-proc -> values
string-compare-ci s1 s2 lt-proc eq-proc gt-proc -> values
Apply LT-PROC, EQ-PROC, GT-PROC to the mismatch index, depending
upon whether S1 is less than, equal to, or greater than S2.
The "mismatch index" is the largest index i such that for
every 0 <= j < i, s1[j] = s2[j] -- that is, I is the first
position that doesn't match. If S1 = S2, the mismatch index
is simply the length of the strings; we observe the protocol
in this redundant case for uniformity.
substring-compare s1 start1 end1 s2 start2 end2 lt-proc eq-proc gt-proc -> values
substring-compare-ci s1 start1 end1 s2 start2 end2 lt-proc eq-proc gt-proc -> values
The continuation procedures are applied to S1's mismatch index (as defined
above). In the case of EQ-PROC, this is always END1.
string= s1 s2 -> #f or integer
string<> s1 s2 -> #f or integer
string< s1 s2 -> #f or integer
string> s1 s2 -> #f or integer
string<= s1 s2 -> #f or integer
string>= s1 s2 -> #f or integer
If the comparison operation is true, the function returns the
mismatch index (as defined for the previous comparator functions).
string-ci= s1 s2 -> #f or integer
string-ci<> s1 s2 -> #f or integer
string-ci< s1 s2 -> #f or integer
string-ci> s1 s2 -> #f or integer
string-ci<= s1 s2 -> #f or integer
string-ci>= s1 s2 -> #f or integer
Case-insensitive variants.
substring= s1 start1 end1 s2 start2 end2 -> #f or integer
substring<> s1 start1 end1 s2 start2 end2 -> #f or integer
substring< s1 start1 end1 s2 start2 end2 -> #f or integer
substring> s1 start1 end1 s2 start2 end2 -> #f or integer
substring<= s1 start1 end1 s2 start2 end2 -> #f or integer
substring>= s1 start1 end1 s2 start2 end2 -> #f or integer
substring-ci= s1 start1 end1 s2 start2 end2 -> #f or integer
substring-ci<> s1 start1 end1 s2 start2 end2 -> #f or integer
substring-ci< s1 start1 end1 s2 start2 end2 -> #f or integer
substring-ci> s1 start1 end1 s2 start2 end2 -> #f or integer
substring-ci<= s1 start1 end1 s2 start2 end2 -> #f or integer
substring-ci>= s1 start1 end1 s2 start2 end2 -> #f or integer
These variants restrict the comparison to the indicated
substrings of S1 and S2.
string-upper-case? s [start end] -> boolean
string-lower-case? s [start end] -> boolean
STRING-UPPER-CASE? returns true iff the string contains
no lower-case characters. STRING-LOWER-CASE returns true
iff the string contains no upper-case characters.
(string-upper-case? "") => #t
(string-lower-case? "") => #t
(string-upper-case? "FOOb") => #f
(string-upper-case? "U.S.A.") => #t
capitalize-string s [start end] -> string
capitalize-string! s [start end] -> unspecified
Capitalize the string: upcase the first alphanumeric character,
and downcase the rest of the string. CAPITALIZE-STRING returns
a freshly allocated string.
(capitalize-string "--capitalize tHIS sentence.") =>
"--Capitalize this sentence."
(capitalize-string "see Spot run. see Nix run.") =>
"See spot run. see nix run."
(capitalize-string "3com makes routers.") =>
"3com makes routers."
capitalize-words s [start end] -> string
capitalize-words! s [start end] -> unspecified
A "word" is a maximal contiguous sequence of alphanumeric characters.
Upcase the first character of every word; downcase the rest of the word.
CAPITALIZE-WORDS returns a freshly allocated string.
(capitalize-words "HELLO, 3THErE, my nAME IS olin") =>
"Hello, 3there, My Name Is Olin"
More sophisticated capitalisation procedures can be synthesized
using CAPITALIZE-STRING and pattern matchers. In this context,
the REGEXP-SUBSTITUTE/GLOBAL procedure may be useful for picking
out the units to be capitalised and applying CAPITALIZE-STRING to
their components.
string-upcase s [start end] -> string
string-upcase! s [start end] -> unspecified
string-downcase s [start end] -> string
string-downcase! s [start end] -> unspecified
Raise or lower the case of the alphabetic characters in the string.
STRING-UPCASE and STRING-DOWNCASE return freshly allocated strings.
string-take s nchars -> string
string-drop s nchars -> string
string-take-right s nchars -> string
string-drop-right s nchars -> string
STRING-TAKE returns the first NCHARS of STRING;
STRING-DROP returns all but the first NCHARS of STRING.
STRING-TAKE-RIGHT returns the last NCHARS of STRING;
STRING-DROP-RIGHT returns all but the last NCHARS of STRING.
These generalise MIT Scheme's HEAD & TAIL functions.
If these procedures produce the entire string, they may return either
S or a copy of S; in some implementations, proper substrings may share
memory with S.
string-pad s k [char start end] -> string
string-pad-right s k [char start end] -> string
Build a string of length K comprised of S padded on the left (right)
by as many occurences of the character CHAR as needed. If S has more
than K chars, it is truncated on the left (right) to length k. CHAR
defaults to #\space.
If K is exactly the length of S, these functions may return
either S or a copy of S.
string-trim s [char/char-set/pred start end] -> string
string-trim-right s [char/char-set/pred start end] -> string
string-trim-both s [char/char-set/pred start end] -> string
Trim S by skipping over all characters on the left / on the right /
on both sides that satisfy the second parameter CHAR/CHAR-SET/PRED:
- If it is a character CHAR, characters equal to CHAR are trimmed.
- If it is a char set CHAR-SET, characters contained in CHAR-SET
are trimmed.
- If it is a predicate PRED, it is a test predicate that is applied
to the characters in S; a character causing it to return true
is skipped.
CHAR/CHAR/SET-PRED defaults to CHAR-SET:WHITESPACE.
If no trimming occurs, these functions may return either S or a copy of S;
in some implementations, proper substrings may share memory with S.
(string-trim-both " The outlook wasn't brilliant, \n\r")
=> "The outlook wasn't brilliant,"
string-filter s char/char-set/pred [start end] -> string
string-delete s char/char-set/pred [start end] -> string
Filter the string S, retaining only those characters that
satisfy / do not satisfy the CHAR/CHAR-SET/PRED argument. If
this argument is a procedure, it is applied to the character
as a predicate; if it is a char-set, the character is tested
for membership; if it is a character, it is used in an equality test.
If the string is unaltered by the filtering operation, these
functions may return either S or a copy of S.
string-index s char/char-set/pred [start end] -> integer or #f
string-index-right s char/char-set/pred [end start] -> integer or #f
string-skip s char/char-set/pred [start end] -> integer or #f
string-skip-right s char/char-set/pred [end start] -> integer or #f
Note the inverted start/end ordering of index-right and skip-right's
parameters.
Index (index-right) searches through the string from the left (right),
returning the index of the first occurence of a character which
- equals CHAR/CHAR-SET/PRED (if it is a character);
- is in CHAR/CHAR-SET/PRED (if it is a char-set);
- satisfies the predicate CHAR/CHAR-SET/PRED (if it is a procedure).
If no match is found, the functions return false.
The skip functions are similar, but use the complement of the criteria:
they search for the first char that *doesn't* satisfy the test. E.g.,
to skip over initial whitespace, say
(cond ((string-skip s char-set:whitespace) =>
(lambda (i)
;; (string-ref s i) is not whitespace.
...)))
string-prefix-count s1 s2 -> integer
string-suffix-count s1 s2 -> integer
string-prefix-count-ci s1 s2 -> integer
string-suffix-count-ci s1 s2 -> integer
Return the length of the longest common prefix/suffix of the two strings.
This is equivalent to the "mismatch index" for the strings.
substring-prefix-count s1 start1 end1 s2 start2 end2 -> integer
substring-suffix-count s1 start1 end1 s2 start2 end2 -> integer
substring-prefix-count-ci s1 start1 end1 s2 start2 end2 -> integer
substring-suffix-count-ci s1 start1 end1 s2 start2 end2 -> integer
Substring variants.
string-prefix? s1 s2 -> boolean
string-suffix? s1 s2 -> boolean
string-prefix-ci? s1 s2 -> boolean
string-suffix-ci? s1 s2 -> boolean
Is S1 a prefix/suffix of S2?
substring-prefix? s1 start1 end1 s2 start2 end2 -> boolean
substring-suffix? s1 start1 end1 s2 start2 end2 -> boolean
substring-prefix-ci? s1 start1 end1 s2 start2 end2 -> boolean
substring-suffix-ci? s1 start1 end1 s2 start2 end2 -> boolean
Substring variants.
substring? s1 s2 [start end] -> integer or false
substring-ci? s1 s2 [start end] -> integer or false
Return the index in S2 where S1 occurs as a substring, or false.
The returned index is in the range [start,end).
The current implementation uses the Knuth-Morris-Pratt algorithm.
string-fill! s char [start end] -> unspecified
Store CHAR into the elements of S.
This is the R4RS procedure extended to have optional START/END parameters.
string-copy! target tstart s [start end] -> unspecified
Copy the sequence of characters from index range [START,END) in
string S to string TARGET, beginning at index TSTART. The characters
are copied left-to-right or right-to-left as needed -- the copy is
guaranteed to work, even if TARGET and S are the same string.
substring s start [end] -> string
string-copy s [start end] -> string
These R4RS procedures are extended to have optional START/END parameters.
Use STRING-COPY when you want to indicate explicitly in your code that you
wish to allocate new storage; use SUBSTRING when you don't care if you
get a fresh copy or share storage with the original string.
E.g.:
(string-copy "Beta substitution") => "Beta substitution"
(string-copy "Beta substitution" 1 10)
=> "eta subst"
(string-copy "Beta substitution" 5) => "substitution"
SUBSTRING may return a value with shares memory with S.
string-reverse s [start end] -> string
string-reverse! s [start end] -> unspecific
Reverse the string.
reverse-list->string char-list -> string
An efficient implementation of (compose string->list reverse):
(reverse-list->string '(#\a #\B #\c)) -> "cBa"
This is a common idiom in the epilog of string-processing loops
that accumulate an answer in a reverse-order list.
string-concat string-list -> string
Append the elements of STRING-LIST together into a single list.
Guaranteed to return a freshly allocated list. Appears sufficiently
often as to warrant being named.
string-concat/shared string-list -> string
string-append/shared s ... -> string
These two procedures are variants of STRING-CONCAT and STRING-APPEND
that are permitted to return results that share storage with their
parameters. In particular, if STRING-APPEND/SHARED is applied to just
one argument, it may return exactly that argument, whereas STRING-APPEND
is required to allocate a fresh string.
string->list s [start end] -> char-list
The R5RS STRING->LIST procedure is extended to take optional START/END
arguments.
string-null? s -> bool
Is S the empty string?
xsubstring s from [to start end] -> string
This is the "extended substring" procedure that implements replicated
copying of a substring of some string.
S is a string; START and END are optional arguments that demarcate
a substring of S, defaulting to 0 and the length of S (e.g., the whole
string). Replicate this substring up and down index space, in both the
positive and negative directions. For example, if S = "abcdefg", START=3,
and END=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 e f ...
... -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 ...
XSUBSTRING returns the substring of this string beginning at index FROM,
and ending at TO (which defaults to FROM+(END-START)).
You can use XSUBSTRING to perform a variety of tasks:
- To rotate a string left: (xsubstring "abcdef" 2) => "cdefab"
- To rotate a string right: (xsubstring "abcdef" -2) => "efabcd"
- To replicate a string: (xsubstring "abc" 0 7) => "abcabca"
Note that
- The FROM/TO indices give a half-open range -- the characters from
index FROM up to, but not including, index TO.
- The FROM/TO indices are not in terms of the index space for string S.
They are in terms of the replicated index space of the substring
defined by S, START, and END.
It is an error if START=END -- although this is allowed by special
dispensation when FROM=TO.
string-xcopy! target tstart s sfrom [sto start end] -> unspecific
Exactly the same as XSUBSTRING, but the extracted text is written
into the string TARGET starting at index TSTART.
This operation is not defined if (EQ? TARGET S) -- you cannot copy
a string on top of itself.
* Lower-level procedures
The following procedures are useful for writing other string-processing
functions, and are contained in the string-lib-internals package.
parse-start+end proc s args -> [start end rest]
parse-final-start+end proc s args -> [start end]
PARSE-START+END may be used to parse a pair of optional START/END arguments
from an argument list, defaulting them to 0 and the length of some string
S, respectively. Let the length of string S be SLEN.
- If ARGS = (), the function returns (values 0 slen '())
- If ARGS = (i), I is checked to ensure it is an integer, and
that 0 <= i <= slen. Returns (values i slen (cdr rest)).
- If ARGS = (i j ...), I and J are checked to ensure they are
integers, and that 0 <= i <= j <= slen. Returns (values i j (cddr rest)).
If any of the checks fail, an error condition is raised, and PROC is used
as part of the error condition -- it should be the name of the client
procedure whose argument list PARSE-START+END is parsing.
parse-final-start+end is exactly the same, except that the args list
passed to it is required to be of length two or less; if it is longer,
an error condition is raised. It may be used when the optional START/END
parameters are final arguments to the procedure.
check-substring-spec proc s start end -> unspecific
Check values START and END to ensure they specify a valid substring
in S. This means that START and END are exact integers, and
0 <= START <= END <= (STRING-LENGTH S)
If this is not the case, an error condition is raised. PROC is used
as part of error condition, and should be the procedure whose START/END
parameters we are checking.
make-kmp-restart-vector s c= -> vector
Build the Knuth-Morris-Pratt "restart vector," which is useful
for quickly searching character sequences for the occurrence of
string S. C= is a character-equality function used to construct
the restart vector; it is usefully CHAR=? or CHAR-CI=?.
The definition of the restart vector RV for string S is:
If we have matched chars 0..i-1 of S against some search string SS, and
S[i] doesn't match SS[k], then reset i := RV[i], and try again to
match SS[k]. If RV[i] = -1, then punt SS[k] completely, and move on to
SS[k+1] and S[0].
In other words, if you have matched the first i chars of S, but
the i+1'th char doesn't match, RV[i] tells you what the next-longest
prefix of PATTERN is that you have matched.
The following string-search function shows how a restart vector
is used to search. It can be easily adapted to search other character
sequences (such as ports).
(define (find-substring pattern source start end)
(let ((plen (string-length pattern))
(rv (make-kmp-restart-vector pattern char=?)))
;; The search loop. SJ & PJ are redundant state.
(let lp ((si start) (pi 0)
(sj (- end start)) ; (- end si) -- how many chars left.
(pj plen)) ; (- plen pi) -- how many chars left.
(if (= pi plen) (- si plen) ; Win.
(and (<= pj sj) ; Lose.
(if (char=? (string-ref source si) ; Search.
(string-ref pattern pi))
(lp (+ 1 si) (+ 1 pi) (- sj 1) (- pj 1)) ; Advance.
(let ((pi (vector-ref rv pi))) ; Retreat.
(if (= pi -1)
(lp (+ si 1) 0 (- sj 1) plen) ; Punt.
(lp si pi sj (- plen pi))))))))))