/**
* This module contains strategies for operating on strings.
*
* In Stratego, strings are primitive terms, separate from characters
* and character lists. It is recommended that all strings contain only
* ISO-8859-1 (Latin1) characters.
*
* @author Eelco Visser <visser@acm.org>
* @author Karl Trygve Kalleberg <karltk@cs.uu.nl>
*/
module term/string
imports
collection/list/common
strategy/conditional
strategy/iteration
system/io/file
/**
* Primitives
*/
strategies
/**
* Generates a unique new string of the form 'c_d',
* where c is one char and d is a positive integer.
*/
new = prim("SSL_new")
/**
* Generates a unique new string with user-defined prefix
* the form 'sd', where s is a string and d is a positive integer.
* The produced string is *always* unique across a program run.
*
* The prefix comes in front of an increasing number, which is
* separately maintained and started at 0 for each prefix.
*
* Examples (results are shown in comment)
* <newname; debug> "a" // produces "a0"
* ; <newname; debug> "a" // produces "a1"
* ; <newname; debug> "a" // produces "a2"
* ; <newname; debug> "b" // produces "b0"
* ; <newname; debug> "b" // produces "b1"
* ; <newname; debug> "b_2" // produces "b_20"
* ; <newname; debug> "b_2" // produces "b_21"
* ; <newname; debug> "b_a" // produces "b_a0"
* ; <newname; debug> "b_a" // produces "b_a1"
* ; <newname; debug> "a_" // produces "a_0"
* ; <newname; debug> "a_1" // produces "a_10"
*/
newname = ?prefix; prim("SSL_newname", prefix)
/**
* Predicate that checks if the supplied term is a string.
*
* @type String -> String
*/
is-string = ?x; prim("SSL_is_string", x)
/**
* Predicate that checks if the supplied term is a printable character.
*
* @type Char -> Char
*/
is-char = is-int ; is-ascii
/**
* Implodes a list of chars to a string.
*
* @type List(Char) -> String
*/
implode-string = ?x; prim("SSL_implode_string", x)
/**
* Explodes a String to a list of chars.
*
* @type String -> List(Char)
*/
explode-string = ?x; prim("SSL_explode_string", x)
/**
* Wraps a strategy on a list of chars in a strategy on a string.
*
* @param List(Char) -> List(Char)
* @type String -> String
*/
string-as-chars(s) =
explode-string
; s
; implode-string
strategies
/**
* Concats a tuple of two Strings.
*
* @type String * String -> String
*/
conc-strings =
?(_,_) ; strcat
<+ ?""#(xs) ; <concat-strings>xs
/**
* Concats a tuple of two Strings.
*
* @warning doesn't accept tuples with more than two string.
* @type String * String -> String
* @inc strcat-1
* @inc strcat-2
* @inc strcat-3
* @inc strcat-4
*/
strcat =
?(str1, str2); prim("SSL_strcat", str1, str2)
/**
* Concats a list of Strings
*
* @type List(String) -> String
* @inc concat-strings-1
* @inc concat-strings-2
* @inc concat-strings-3
* @inc concat-strings-4
*/
concat-strings =
?strings; prim("SSL_concat_strings", strings)
strategies
/**
* Returns the number of characters in a string.
*
* @type String -> Int
*/
string-length =
strlen
/**
* Returns the number of characters in a string.
*
* @type String -> Int
* @inc strlen-1
* @inc strlen-2
* @inc strlen-3
*/
strlen =
?str; prim("SSL_strlen", str)
strategies
/**
* Escapes double quotes, backslash and linefeed to C like escape sequences.
*
* @type String -> String
* @inc escape-test1
*/
escape = string-as-chars(escape-chars)
/**
* Escapes double quotes, backslash and linefeed to C like escape sequences.
*
* @type List(Char) -> List(Char)
*/
escape-chars =
escape-chars(Escape)
/** @internal */
escape-chars(s) =
rec x(
(s; [id, id | x])
<+ [id | x]
<+ []
)
/**
* Unescapes double quotes, backslash, linefeed and tabs.
*
* @type String -> String
* @inc unescape-test1
* @inc unescape-test2
* @inc unescape-test3
* @inc unescape-test4
*/
unescape = string-as-chars(unescape-chars)
/**
* Unescapes characters using a specified unescape strategy.
*
* @param List(Char) -> List(Char)
* @type List(Char) -> List(Char)
*/
unescape-chars(s) =
rec x(
try(s)
; ([id | x] <+ [])
)
/**
* Unescapes double quotes, backslash, linefeed and tabs.
*
* @type List(Char) -> List(Char)
*/
unescape-chars =
unescape-chars(UnEscape)
/**
* @type List(Char) -> List(Char)
*/
Escape =
Escape-double-quote
+ Escape-backslash
+ Escape-linefeed
+ Escape-carriage-return
/** @internal */
Escape-double-quote : ['"' | cs ] -> ['\', '"' | cs ]
/** @internal */
Escape-single-quote : ['\'' | cs ] -> ['\', '\'' | cs ]
/** @internal */
Escape-backslash : ['\' | cs ] -> ['\', '\' | cs ]
/** @internal */
Escape-linefeed : ['\n' | cs ] -> ['\', 'n' | cs ]
/** @internal */
Escape-carriage-return : ['\r' | cs ] -> ['\', 'r' | cs ]
/**
* @type List(Char) -> List(Char)
*/
UnEscape =
UnEscape-double-quote
+ UnEscape-backslash
+ UnEscape-linefeed
+ UnEscape-tab
+ UnEscape-carriage-return
/** @internal */
UnEscape-double-quote : ['\', '"' | cs ] -> ['"' | cs ]
/** @internal */
UnEscape-single-quote : ['\', '\'' | cs ] -> ['\'' | cs ]
/** @internal */
UnEscape-backslash : ['\', '\' | cs ] -> ['\' | cs ]
/** @internal */
UnEscape-carriage-return : ['\', 'r' | cs ] -> ['\r' | cs ]
/** @internal */
UnEscape-linefeed : ['\', 'n' | cs ] -> ['\n' | cs ]
/** @internal */
UnEscape-tab : ['\', 't' | cs ] -> ['\t' | cs ]
/** @internal */
UnEscape(|c) :
['\', c | cs ] -> [c | cs ]
strategies
/** @internal */
escape(esc : (List(String) -> List(String)) * List(String) -> List(String) ) =
explode-string;
rec x(esc(x) <+ [id | x] <+ []);
implode-string
/**
* @inc escape-test2
*/
cify = escape(Cify)
/** @internal */
uncify = escape(UnCify)
/** @internal */
UnCify(x) = UnCify1(x) <+ UnCify2(x) <+ UnCify3(x)
rules
/** @internal */
Cify(x) : ['\'' | cs] -> ['_', 'p', '_' | <x>cs]
/** @internal */
Cify(x) : ['-' | cs] -> ['_' | <x>cs]
/** @internal */
Cify(x) : ['_' | cs] -> ['_', '_' | <x>cs]
/** @internal */
Cify(x) : ['"' | cs] -> ['_', 'q', '_' | <x>cs]
/** @internal */
Cify(x) : ['\' | cs] -> ['_', 'b', '_' | <x>cs]
/** @internal */
UnCify1(x) : ['_', 'b', '_' | cs] -> ['\' | <x>cs]
/** @internal */
UnCify1(x) : ['_', 'q', '_' | cs] -> ['"' | <x>cs]
/** @internal */
UnCify1(x) : ['_', 'p', '_' | cs] -> ['\'' | <x>cs]
/** @internal */
UnCify2(x) : ['_', '_' | cs] -> ['_' | <x>cs]
/** @internal */
UnCify3(x) : ['_' | cs] -> ['-' | <x>cs]
/**
* Quoting Literals
*/
strategies
/**
* param () -> Char
* @type String -> String
*/
quote(c) = string-as-chars(quote-chars(c))
/**
* param () -> Char
* @type List(Char) -> List(Char)
*/
quote-chars(c) = <quote-chars> (<c> (), <id>)
/**
* @type Char * List(Char) -> List(Char)
*/
quote-chars:
(c, chars) -> <conc> ([c | chars], [c])
/**
* Adds single quotes to the start and end of a list
* of characters.
*
* @type List(Char) -> List(Char)
*/
single-quote-chars = <quote-chars> ('\'', <id>)
/**
* Adds double quotes to the start and end of a
* list of characters.
*
* @type List(Char) -> List(Char)
*/
double-quote-chars = <quote-chars> ('"' , <id>)
/**
* @inc single-quote-test
*/
single-quote = string-as-chars(single-quote-chars)
/**
* Adds double quotes to the start and end of
* a string.
*
* @type String -> String
*/
double-quote = string-as-chars(double-quote-chars)
/**
* Unquoting
*/
strategies
/**
* Removes the first and last character of a string.
* The characters must satisfy s.
*
* @param Char ->? _
* @type String -> String
*/
unquote(s) =
string-as-chars(unquote-chars(s))
/**
* Removes the first and last character of a string.
* The first character must satisfy s1.
* The last character must satisfy s2.
*
* @param Char ->? _
* @param Char ->? _
* @type String -> String
*/
unquote(s1, s2) =
string-as-chars(unquote-chars(s1, s2))
/**
* Removes the first and last character of a list of characters.
* The characters must satisfy s.
*
* @param Char ->? _
* @type List(Char) -> List(Char)
*/
unquote-chars(s) =
unquote-chars(s, s)
/**
* Removes the first and last character of a list of characters.
* The first character must satisfy s1.
* The last character must satisfy s2.
*
* @param Char ->? _
* @param Char ->? _
* @type List(Char) -> List(Char)
*/
unquote-chars(s1, s2) =
where(Hd; s1)
; where(last; s2)
; Tl
; init
/**
* Removes single quotes from the start and end of a list
* of characters. Fails if the list is not properly
* quoted.
*
* @type List(Char) -> List(Char)
*/
un-single-quote-chars = unquote-chars(?'\'')
/**
* Removes double quotes from the start and end of a list
* of characters. Fails if the list is not properly
* quoted.
*
* @type List(Char) -> List(Char)
*/
un-double-quote-chars = unquote-chars(?'"' )
/**
* Removes single quotes from the start and end of a string
* Fails if the string is not properly quoted.
*
* @type String -> String
*/
un-single-quote = string-as-chars(un-single-quote-chars)
/**
* Removes double quotes from the start and end of a string
* Fails if the string is not properly quoted.
*
* @type String -> String
*/
un-double-quote = string-as-chars(un-double-quote-chars)
strategies
/** Predicate that checks if a string is double quoted, i.e. quoted
* with double quotation marks (").
*
* @inc is-double-quote 1
* @inc is-double-quote 2
* @inc is-double-quote 3
* @inc is-double-quote 4
* @inc is-double-quote 5
* @inc is-double-quote 6
*
* @type String -> String
*/
is-double-quoted =
is-quoted(|'"')
/**
* Predicate that checks a string is quoted with single quotes.
*
* @type String -> String
*/
is-single-quoted =
is-quoted(|'\'')
/**
* Predicate that checks if a character list is double quoted. That
* is, if the first and last character are both double quotes.
*
* @type List(Char) -> List(Char)
*/
is-double-quoted-chars =
is-quoted-chars(|'"')
/**
* Predicate that checks if a character list is single quoted. That is,
* if the first and last character are both single quotes.
*
* @type List(Char) -> List(Char)
*/
is-single-quoted-chars =
is-quoted-chars(|'\'')
/**
* Predicate that checks if a string is quoted with the given character.
*
* The character c is the quotation character to check for.
*
* @param Char
* @type String -> String
*/
is-quoted(|c) =
where(
explode-string
; is-quoted-chars(|c)
)
/**
* Predicate that checks if a list of characters is quoted with
* the character given by the term argument. That is, if the
* first and last character of the list are both equal to c.
*
* @param Char
* @type List(Char) -> List(Char)
*/
is-quoted-chars(|c) =
where(?[c | <last>]; ?c)
strategies
/**
* @obsolete use string-to-int
*/
string-to-num =
obsolete(!"string-to-num; use string-to-int")
; string-to-int
strategies
/**
* Converts all characters of this string to lower case
*
* @type String -> String
*/
lower-case = string-as-chars(lower-case-chars)
/**
* Converts all characters of this string to upper case
*
* @inc upper-case-test
*/
upper-case = string-as-chars(upper-case-chars)
/**
* Converts a list of characters to lower case.
*
* Example: <explode-string ; lower-case-char ; implode-string> "STRATEGO" => "stratego"
*
* @type List(Char) -> List(Char)
*/
lower-case-chars = map(to-lower)
/**
* Converts a list of characters to upper case.
*
* Example: <explode-string ; upper-case-chars ; implode-string> "stratego" => "STRATEGO"
*
* @type List(Char) -> List(Char)
*/
upper-case-chars = map(to-upper)
strategies
/**
* Breaks a string into tokens.
*
* Given a list of possible delimiters (characters that
* separate tokens), string-tokenize returns a list of
* tokens for a given string.
*
* @param List of separate characters
* @type String -> List(String)
*/
string-tokenize(|sepchars) =
string-tokenize({c: ?c; <fetch(?c)> sepchars})
/** @internal */
string-tokenize(sep) =
explode-string
; list-tokenize(sep)
; map(implode-string)
/**
* @inc tokenize-test1
* @inc tokenize-test2
* @inc tokenize-test3
* @inc tokenize-test4
* @inc tokenize-test5
*
* @type (List(Char), String) -> List(String)
*/
string-tokenize =
?(sepchars, <id>)
; string-tokenize(|sepchars)
strategies
/**
* @param s () -> Char
* @type String * Int -> String
* @inc padd-with-test
*/
padd-with(s) =
<align-right> (<s> (), <Fst>, <Snd>)
/**
* @type Char * String * Int -> String
*/
align-right :
(c, s, n) -> <conc-strings> (add, s)
where <align-helper(|c, n)> s => add
/**
* @type Char * String * Int -> String
*/
align-left :
(c, s, n) -> <conc-strings> (s, add)
where <align-helper(|c, n)> s => add
/**
* @type Char * String * Int -> String
*/
align-center :
(c, s, n) -> <concat-strings> [add-before, s, add-after]
where <string-length> s => l
; <subt> (n, l) => whitespace
; < even
< div2; Dupl
+ dec; div2; Dupl; (inc, id)
> whitespace => (before, after)
; <align-helper(|c, <add> (l, before))> s => add-before
; <align-helper(|c, <add> (l, after ))> s => add-after
/**
* @param c Char
* @param n Int
* @type String -> String
*/
align-helper(|c, n) =
string-length
; <subt> (n, <id>)
; if pos then
<copy-char> (<id>, c)
else
!""
end
div2 = <div> (<id>, 2)
rules
/** @internal */
SplitInit : x -> ([], [], x)
/** @internal */
SplitExit :
(xs, cs, []) ->
<reverse> [<reverse; implode-string> cs|xs]
/** @internal */
SplitNext :
(xs, cs, [32|ys]) ->
([<reverse; implode-string> cs | xs], [], ys)
/** @internal */
SplitNext :
(xs, cs, [y|ys]) -> (xs, [y|cs], ys)
where <not(eq)> (y, 32)
strategies
/**
* Splits a string at each occurrence of a whitespace. The
* resulting list of string does not contain any of the
* whitespaces. Two adjacent whitespaces will result in
* an empty string in the result.
*
* Example: <split-at-space> "foo bar" => ["foo", "", "bar"]
*
* @type String -> List(String)
*/
split-at-space =
explode-string;
SplitInit;
rec x(SplitExit <+ SplitNext; x)
/**
* @inc split-at-dot-test
*/
split-at-dot =
split(![],explode-string)
; rec r( \(int,[46|frac]) -> (int,frac)\
<+ \(int,[i|rest]) -> <r>(<at-end(![i])>int,rest)\
<+ (id,Nil))
; (implode-string,implode-string)
// fails if sub isn't in l
/**
* @inc split-before-test
*/
split-before = ?(l,sub);
<explode-string>sub => sub';
<explode-string>l;
\ x -> ([],x) \;
rec r( \ x@(_,x') -> x where <left-match>(x',sub') \ <+
\ (x',[x|xs]) -> <r>(<at-end(![x])>x',xs) \ );
(implode-string,implode-string)
// results in (l,"") if sub isn't in l
/**
* @inc split-after-test
*/
split-after = ?(l,sub);
<explode-string>sub => sub';
<explode-string>l;
\ x -> ([],x) \;
rec r( (id,[]) <+
\ (x,x') -> (<conc>(x,sub'),<left-match>(x',sub')) \ <+
\ (x',[x|xs]) -> <r>(<at-end(![x])>x',xs) \ );
(implode-string,implode-string)
/**
* Succeeds if the string produced by s is a substring
* of the argument string.
*
* @param _ -> String
* @type String -> _
*/
is-substring(s) =
explode-string;?long;
s;explode-string => sub;
(<eq>(sub,long)
<+
<rec r({a,xs:
?a@[_|xs]; (<left-match>(a,sub) <+ <r>xs)
})>long)
// <copy-char> (3, 32) -> " "
/**
* @inc copy-char-test
*/
copy-char =
copy; implode-string
/**
* Sorts a list of strings lexicographically, ignoring case, ascending.
*
* @inc string-sort-test-a
* @inc string-sort-test-b
* @inc string-sort-test-c
*
* @type List(String) -> List(String)
*/
string-sort =
map(explode-string)
; qsort(string-lt)
; map(implode-string)
/**
* Sorts a list of strings lexicographically ignoring case, descending.
*
* @type List(String) -> List(String)
*/
string-sort-desc =
map(explode-string)
; qsort(string-gt)
; map(implode-string)
strategies
/**
* Compares two strings or lists of characters lexicographically and succeeds if
* the first one is larger than the second, ignoring case.
*
* @type (List(Char), List(Char)) -> 1
* @type (String, String) -> 1
*/
string-gt =
try((explode-string, explode-string));
strcasecmp; ?1
/**
* Compares two strings or lists of characters lexicographically and succeeds if
* the first one is smaller than the second, ignoring case.
*
* @type (List(Char), List(Char)) -> 1
* @type (String, String) -> 1
*/
string-lt =
try((explode-string, explode-string));
strcasecmp; ?-1
/**
* Succeeds if the first String starts with the second String.
*
* If the strategy succeeds, the current term is left untouched.
*
* Example: <string-starts-with(|"foo")> "foobar"
*
* @type String -> String
*/
string-starts-with(|start) =
where(<left-match> (<explode-string>, <explode-string> start))
/**
* Succeeds if the first String ends with the second String.
*
* If the strategy succeeds, the current term is left untouched.
*
* Example: <string-ends-with(|"bar")> "foobar"
*
* @type String -> String
*/
string-ends-with(|end) =
where(<right-match> (<explode-string>, <explode-string> end))
/**
* Succeeds if the first list of terms starts with the second list of terms.
*
* Returns the suffix of the first list that is not matched.
*
* @type (List(a), List(a)) -> List(a)
*/
left-match =
rec r(
\ ([l|ls],[l|rs]) -> <r> (ls,rs) \
<+ (id,[])
)
; Fst
/**
* Succeeds if the first list of terms ends with the second list of terms.
*
* Returns the prefix of the first list that is not matched.
*
* @type (List(a), List(a)) -> List(a)
*/
right-match =
(reverse, reverse)
; left-match
; reverse
/**
* @inc strcmp
*/
strcmp = rec r(
\([x|xs],[x|ys]) -> <r>(xs,ys)\
<+ \([],[]) -> 0\
<+ \([x|_],[y|_]) -> -1 where <lt>(x,y)\
<+ \([],[_|_]) -> -1\
// these last two cases are handled faster by just !1
// <+ \([_|_],[]) -> 1\
// <+ \([x|_],[y|_]) -> 1\
<+ !1
)
/**
* Compares the n first characters of two character lists, n
* is given by the last argument. If the two lists are identical,
* the integer 0 is returned, otherwise the integer 1 is returned.
*
* @type List(Char) * List(Char) * Int -> Int
*/
strncmp = rec r
( \(_,_,0) -> 0\
<+ \([x|xs],[x|ys],i) -> <r>(xs,ys,<subt>(i,1))\
<+ \([],[],_) -> 0\
<+ \([x|_],[y|_],_) -> -1 where <lt>(x,y)\
<+ \([],[_|_],_) -> -1\
<+ !1
)
/**
* Compares two lists of characters lexicographically, ignoring case.
*
* Returns:
* -1 if first is smaller than the second.
* 0 if the strings are equal.
* 1 if the first is larger than the second.
*
* @type (List(Char), List(Char)) -> -1, 0,1 or -1.
*/
strcasecmp = rec r
( \([x|xs],[y|ys]) -> <r>(xs,ys) where <case-char(eq)>(x,y)\
<+ \([],[]) -> 0\
<+ \([x|_],[y|_]) -> -1 where <case-char(lt)>(x,y)\
<+ \([],[_|_]) -> -1\
<+ !1
)
strategies
/**
* Splits a string into a list of tokens, including the empty ones.
*
* This strategy differs from string-tokenize in that it produces an empty ""
* token when two separator chars are encountered, or after a final separator.
*
* Example:
* <string-tokenize-keep-empty(|['\n'])> "fu\n\nbar\n" => ["fu","","bar",""]
*
* @param The list of characters that may separate two tokens.
* @type sepChars [char]
* @type string -> [string]
* @since 0.9.5
*/
string-tokenize-keep-empty(|sepChars) =
explode-string
; rec x( ( split-fetch(<elem> (<id>, sepChars))
; \ (tchars, cs) -> [tchars|<x> cs] \ )
<+ ![<id>])
; map(implode-string)
/**
* Splits a string into a list of tokens, keeping the separating elements as
* 1-char-strings within the results list, *and* also keeping the empty
* "" tokens.
*
* Example:
* <string-tokenize-keep-all(|['\n'])> "fu\n\nbar\n" => ["fu","\n","","\n","bar","\n",""]
*
* @param The list of characters that may separate two tokens.
* @type sepChars [char]
* @type string -> [string]
* @since 0.9.5
*/
string-tokenize-keep-all(|sepChars) =
explode-string
; rec x( ( split-fetch-keep(where(<elem> (<id>, sepChars)))
; \ (tchars, s, cs) -> [tchars|[[s]|<x> cs]] \ )
<+ ![<id>])
; map(implode-string)
/**
* Gets all newline(\n, \r\n or \r)-separated lines in a string.
* If the string is ended by a newline, the last element of the returned list
* is the empty string.
*
* @type string -> [string]
* @since 0.9.4
*/
get-lines =
explode-string
; let split-at-newlines = {tl:
at-suffix(
(['\n' | tl] -> [])
<+ (['\r', '\n' | tl] -> [])
<+ (['\r' | tl] -> [])
)
; ![<id> | <split-at-newlines> tl]
<+ ![<id>]
}
in split-at-newlines
end
; map(implode-string)
/**
* Applies a strategy to all lines in a string.
*
* Lines are expected to be separated by a single '\n'.
* The last line may be ended by a newline, or not. In the former case, the
* last line in the resulting string will also be ended by a newline,
* otherwise not.
* Initial or intermediate newlines are preserved.
* The strategy is applied to each line (even the empty ones), *excluding*
* the newline character. The newline is appended again after application of s
*
* @param The strategy that should be applied to each line.
* @type s string -> string
* @type string -> string
* @since 0.9.4
*/
all-lines(s) =
is-string
; string-tokenize-keep-empty(|['\n'])
; at-last( \ [""] -> [] \ // Do not run s on a final empty line.
< where(!["\n"] => laststring) // ... but remember that the second last line was delimited by a newline
+ where(![] => laststring)) // ... or not.
; map(s)
; <separate-by> ("\n", <id>)
; <conc> (<id>, laststring) // Restore the final newline, if there was one.
; concat-strings
/**
* Makes one newline-separated string out of a list of strings.
*
* @since 0.9.4
*/
lines =
separate-by(!"\n")
; <conc> (<id>, ["\n"])
; concat-strings
/**
* Indents every line in a string with spaces.
*
* @param The number of spaces to be put in front of every line.
* @type n int
* @type string -> string
* @since 0.9.4
*/
indent-text(|n) =
where(<copy-char> (n, ' ') => prefix)
; prefix-lines(|prefix)
/**
* Prefixes all lines in a string with a string.
*
* @param The prefix.
* @type p string
* @type string -> string
* @since 0.9.4
*/
prefix-lines(|p) =
all-lines(<conc-strings> (p, <id>))
strategies
/**
* Trims leading characters from a string. The characters to
* to trim are given by the strategy argument.
*
* Removes from the left-hand side all characters matched by
* strategy s.
*
* Example: <ltrim-chars(?' ')> " fred " => "fred "
*
* @param should match all characters to be removed
* @type String -> String
* @since 0.9.5
*/
ltrim-chars(s) = string-as-chars(ltrim(s))
/**
* Trims trailing characters from a string.
*
* Removes from the right-hand side all characters matched by
* strategy s.
*
* Example: <rtrim-chars(?' ')> " fred " => " fred"
*
* @param should match on all characters to be removed
* @type String -> String
* @since 0.9.5
*/
rtrim-chars(s) = string-as-chars(rtrim(s))
/**
* Trims leading and trailing characters from a string. The
* characters to trim is given by the strategy argument.
*
* Removes from both ends all characters matched by strategy
* s.
*
* Example: <trim-chars(?' ')> " fred " => "fred"
*
* @param should match all characters to be removed
* @type String -> String
* @since 0.9.5
*/
trim-chars(s) = string-as-chars(trim(s))
/**
* Succeeds if the character is whitespace.
*
* Whitespace is a space (32), or a tab (9)
*
* @type Char -> Char
* @since 0.9.5
*/
is-whitespace = test('\t' + ' ')
/**
* Trims all trailing whitespace in a (single-line) string.
*
* @type String -> String
* @since 0.9.5
*/
trim-trailing-whitespace =
rtrim-chars(is-whitespace)
/**
* Trims all leading whitespace in a (single-line) string.
*
* @type String -> String
* @since 0.9.5
*/
trim-leading-whitespace =
ltrim-chars(is-whitespace)
/**
* Trims all trailing and leading whitespace in a (single-line) string.
*
* @type String -> String
* @since 0.9.5
*/
trim-whitespace =
trim-chars(is-whitespace)
strategies
/**
* Some shuffling strategies
* @internal
*/
shuffle-forced =
where(length; <gt> (<id>, 1))
; where(at-suffix(?[x,y|xs]; where(not(<eq> (x, y)))))
; rec x({orig: ?orig; shuffle; (where(<eq> (orig, <id>)) < x + id)})
<+ id
/** @internal */
shuffle =
where(is-list)
; map(!(<next-random> (), <id>))
; sort-list(SortL((Fst, Fst); gt))
; map(Snd)
/** @internal */
shuffle-word-mids =
string-tokenize-keep-all(|[' ', '.', ','])
; map(shuffle-word-mid)
; concat-strings
/** @internal */
shuffle-word-mid =
where(is-string)
; (where(strlen; <lt> (<id>, 3))
<+ explode-string
; ?[i|cs]
; <reverse> cs
; ?[l|mid]
; where(<shuffle-forced; reverse> mid => shuffledmid)
; <concat; implode-string> [[i],shuffledmid, [l]])
strategies
/**
* @inc is-alphanum-test
*/
is-alphanum = is-num + is-alpha
/**
* Succeeds if the character is part of the lowercase or
* uppercase alphabet.
*
* @type Char -> Char
*/
is-alpha = is-upper + is-lower
/**
* Succeeds if applied to a character that is a hexidecimal
* digit, i.e. either of the characters in the range '0'-'9'
* or 'A'-'F' or 'a'-'f'.
*
* @type Char -> Char
*/
is-hexnum =
is-char
; (
is-num
+ where(<leq-leq> ('a', <id>, 'f'))
+ where(<leq-leq> ('A', <id>, 'F'))
)
/**
* @inc is-num-test
* @inc is-not-num-test
*/
is-num =
where(<leq-leq> ('0', <id>, '9'))
/**
* Succeeds if this character is upper-case.
*
* @type Char -> Char
*/
is-upper =
where(<leq-leq> ('A', <id>, 'Z'))
/**
* Succeeds if this character is lower-case.
*
* @type Char -> Char
*/
is-lower =
where(<leq-leq> ('a', <id>, 'z'))
/**
* Succeeds if this string only contains printable ASCII characters.
*
* The letters 32 through 126, in total 96 characters, are defined
* as printable.
*
* @type Char -> Char
*/
is-ascii =
where(<leq-leq> ( 32, <id>, 127))
/**
* Applies a strategy to the upper-case variants of two characters.
*
* This is useful for doing case-insenstive operations.
*
* @param (Char, Char) -> a
* @type (Char, Char) -> a
*/
case-char(s) = (to-upper, to-upper); s
/**
* Ensures that a character is upper-case.
*
* This strategy returns the input character if this
* character is already upper-case.
*
* @type Char -> Char
*/
to-upper =
is-lower < <subt> (<id>, 32) + id
/**
* Ensures that a character is lower-case.
*
* This strategy returns the input character if this
* character is already lower-case.
*
* @type Char -> Char
*/
to-lower = is-upper < <add> (<id>, 32) + id
/**
* Replaces one substring with another in a string.
*
* Example: <string-replace(|"foo", "bar")> "foobarfoo" gives "barbarbar"
*
* @param old - the old substring which is to be excised
* @param new - the new subtring which is to be inserted
* @type String -> String
*
* @author Karl Trygve Kalleberg <karltk@stratego.org>
*/
string-replace(|old, new) =
!