/** * 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@strategoxt.org> */ 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) = !([], <explode-string>, <explode-string> old) ; internal-string-replace(|<explode-string> old, <explode-string ; reverse> new) ; reverse ; implode-string strategies /** @internal */ internal-string-replace(|old, new) = ?(pre, [ hd | tl ], cur) ; ( !cur => [hd | ctl] ; !(pre, tl, ctl) ; internal-string-replace-found(|old, new) ; internal-string-replace(|old, new) <+ !([ hd | pre ], tl, cur) ; internal-string-replace(|old, new) ) /** @internal */ internal-string-replace(|old, new): (pre, [], _) -> pre /** @internal */ internal-string-replace-found(|old, new) = ?(pre, [ hd | tl ], cur) ; !cur => [ hd | ctl ] ; !(pre, tl, ctl) ; internal-string-replace-found(|old, new) /** @internal */ internal-string-replace-found(|old, new): (pre, rest, []) -> (<conc> (new, pre), rest, old)