(********************************************************************) (* *) (* string.s7i String support library *) (* Copyright (C) 1989 - 2013 Thomas Mertes *) (* *) (* This file is part of the Seed7 Runtime Library. *) (* *) (* The Seed7 Runtime Library is free software; you can *) (* redistribute it and/or modify it under the terms of the GNU *) (* Lesser General Public License as published by the Free Software *) (* Foundation; either version 2.1 of the License, or (at your *) (* option) any later version. *) (* *) (* The Seed7 Runtime Library is distributed in the hope that it *) (* will be useful, but WITHOUT ANY WARRANTY; without even the *) (* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *) (* PURPOSE. See the GNU Lesser General Public License for more *) (* details. *) (* *) (* You should have received a copy of the GNU Lesser General *) (* Public License along with this program; if not, write to the *) (* Free Software Foundation, Inc., 51 Franklin Street, *) (* Fifth Floor, Boston, MA 02110-1301, USA. *) (* *) (********************************************************************) const proc: (ref string: dest) ::= (in string: source) is action "STR_CREATE"; const proc: (inout string: dest) := (in string: source) is action "STR_CPY"; (** * Default value of ''string'' (""). *) const string: (attr string) . value is ""; (** * String multiplication. * The string ''stri'' is concatenated to itself such that in total * ''factor'' strings are concatenated. * "LA" mult 3 returns "LALALA" * "WORD" mult 0 returns "" * @return the result of the string multiplication. * @exception RANGE_ERROR If the factor is negative. *) const func string: (in string: stri) mult (in integer: factor) is action "STR_MULT"; (** * Pad a ''string'' with spaces at the left side up to a given length. * @return the ''string'' left padded with spaces. *) const func string: (in string: stri) lpad (in integer: length) is action "STR_LPAD"; (** * Pad a ''string'' with zeroes at the left side up to a given length. * @return the ''string'' left padded with zeroes. *) const func string: (in string: stri) lpad0 (in integer: length) is action "STR_LPAD0"; (** * Pad a ''string'' with spaces at the right side up to a given length. * @return the ''string'' right padded with spaces. *) const func string: (in string: stri) rpad (in integer: length) is action "STR_RPAD"; (** * Concatenate two strings. * This operator is intended for normal expressions. Its parameters * are not converted to ''string''. * @return the result of the concatenation. *) const func string: (in string: stri1) & (in string: stri2) is action "STR_CAT"; (** * Concatenate two strings. * This operator is intended for write statements. The functions * [[enable_io#enable_io(in_type)|enable_io]] respectively * [[enable_output#enable_output(in_type)|enable_output]] overload * the <& operator for many types. This overloaded operators * optionally convert parameters to ''string''. * @return the result of the concatenation. *) const func string: (in string: stri1) <& (in string: stri2) is action "STR_CAT"; (** * Get a character, identified by an ''index'', from a ''string''. * The first character has the ''index'' 1. * "abcde"[1] returns 'a' * "abcde"[5] returns 'e' * "abcde"[0] raises INDEX_ERROR * "abcde"[6] raises INDEX_ERROR * @return the character specified with the ''index''. * @exception INDEX_ERROR If the ''index'' is less than 1 or * greater than the length of the ''string''. *) const func char: (in string: stri) [ (in integer: index) ] is action "STR_IDX"; (** * Get a substring beginning at a ''start'' position. * The first character in a ''string'' has the position 1. * "abcde"[3 ..] returns "cde" * "abcde"[6 ..] returns "" * ""[1 ..] returns "" * "abcde"[1 ..] returns "abcde" * "abcde"[0 ..] raises INDEX_ERROR * @param stri Source string from which the substring is created. * @param start Start position which must be >= 1. * @return the substring beginning at the ''start'' position. * @exception INDEX_ERROR The ''start'' position is negative or zero. * @exception MEMORY_ERROR Not enough memory to represent the result. *) const func string: (in string: stri) [ (in integer: start) .. ] is action "STR_TAIL"; (** * Get a substring ending at a ''stop'' position. * The first character in a ''string'' has the position 1. * "abcde"[.. 4] returns "abcd" * "abcde"[.. 6] returns "abcde" * ""[.. 5] returns "" * "abcde"[.. 0] returns "" * "abcde"[.. -1] raises INDEX_ERROR * @param stri Source string from which the substring is created. * @param stop Stop position which must be >= 0. * @return the substring ending at the ''stop'' position. * @exception INDEX_ERROR The ''stop'' position is negative. * @exception MEMORY_ERROR Not enough memory to represent the result. *) const func string: (in string: stri) [ .. (in integer: stop) ] is action "STR_HEAD"; (** * Get a substring from a ''start'' position to a ''stop'' position. * The first character in a ''string'' has the position 1. * "abcde"[2 .. 4] returns "bcd" * "abcde"[2 .. 7] returns "bcde" * "abcde"[4 .. 3] returns "" * "abcde"[4 .. 2] raises INDEX_ERROR * "abcde"[6 .. 8] returns "" * "abcde"[1 .. 3] returns "abc" * "abcde"[0 .. 3] raises INDEX_ERROR * "abcde"[1 .. 0] returns "" * "abcde"[1 .. -1] raises INDEX_ERROR * @param stri Source string from which the substring is created. * @param start Start position which must be >= 1. * @param stop Stop position which must be >= pred(''start''). * @return the substring from position ''start'' to ''stop''. * @exception INDEX_ERROR The ''start'' position is negative or zero, or * the ''stop'' position is less than pred(''start''). * @exception MEMORY_ERROR Not enough memory to represent the result. *) const func string: (in string: stri) [ (in integer: start) .. (in integer: stop) ] is action "STR_RANGE"; (** * Get a substring from a ''start'' position with a given ''length''. * The first character in a ''string'' has the position 1. * "abcde"[2 len 3] returns "bcd" * "abcde"[2 len 5] returns "bcde" * "abcde"[3 len 0] returns "" * "abcde"[6 len 2] returns "" * "abcde"[3 len -1] raises INDEX_ERROR * "abcde"[0 len 2] raises INDEX_ERROR * @param stri Source string from which the substring is created. * @param start Start position which must be >= 1. * @param length Requested maximum length of the substring. * @return the substring from the ''start'' position with up to ''length'' characters. * @exception INDEX_ERROR The ''start'' position is negative or zero, or * the ''length'' is negative. * @exception MEMORY_ERROR Not enough memory to represent the result. *) const func string: (in string: stri) [ (in integer: start) len (in integer: length) ] is action "STR_SUBSTR"; (** * Get a substring from a ''start'' position with a guaranteed ''length''. * The first character in a string has the position 1. * "abcde"[2 fixLen 3] returns "bcd" * "abcde"[3 fixLen 0] returns "" * "abcde"[2 fixLen 5] raises INDEX_ERROR * "abcde"[6 fixLen 2] raises INDEX_ERROR * "abcde"[3 fixLen -1] raises INDEX_ERROR * "abcde"[0 fixLen 2] raises INDEX_ERROR * ""[1 fixLen 0] raises INDEX_ERROR * @param stri Source string from which the substring is created. * @param start Start position between 1 and length(''stri''). * @param length Guaranteed length of the substring. * @return the substring from the ''start'' position with ''length'' characters. * @exception INDEX_ERROR The ''length'' is negative, or the ''start'' position * is outside of the string, or the substring from the * ''start'' position has less than ''length'' characters. * @exception MEMORY_ERROR Not enough memory to represent the result. *) const func string: (in string: stri) [ (in integer: start) fixLen (in integer: length) ] is action "STR_SUBSTR_FIXLEN"; (** * Append the string ''extension'' to ''destination''. * @exception MEMORY_ERROR Not enough memory for the concatenated * string. *) const proc: (inout string: destination) &:= (in string: extension) is action "STR_APPEND"; (** * Append the char ''extension'' to ''destination''. * @exception MEMORY_ERROR Not enough memory for the concatenated * string. *) const proc: (inout string: destination) &:= (in char: extension) is action "STR_PUSH"; (** * Assign char ''source'' to the ''position'' of the ''destination''. * A @:= [B] C; * is equivalent to * A := A[..pred(B)] & str(C) & A[succ(B)..]; * @exception INDEX_ERROR If ''position'' is negative or zero, or * a character beyond ''destination'' would be overwritten * (''position'' > length(''destination'') holds). *) const proc: (inout string: destination) @:= [ (in integer: position) ] (in char: source) is action "STR_ELEMCPY"; (** * Assign string ''source'' to the ''position'' of the ''destination''. * A @:= [B] C; * is equivalent to * A := A[..pred(B)] & C & A[B+length(C)..]; * @exception INDEX_ERROR If ''position'' is negative or zero, or * if ''destination'' is smaller than ''source'', or * characters beyond ''destination'' would be overwritten * (''position'' + length(''source'') > succ(length(''destination'')) * holds). *) const proc: (inout string: destination) @:= [ (in integer: position) ] (in string: source) is action "STR_POSCPY"; (** * Check if two strings are equal. * @return TRUE if both strings are equal, * FALSE otherwise. *) const func boolean: (in string: stri1) = (in string: stri2) is action "STR_EQ"; (** * Check if two strings are not equal. * @return FALSE if both strings are equal, * TRUE otherwise. *) const func boolean: (in string: stri1) <> (in string: stri2) is action "STR_NE"; (** * Check if stri1 is less than stri2. * @return TRUE if stri1 is less than stri2, * FALSE otherwise. *) const func boolean: (in string: stri1) < (in string: stri2) is action "STR_LT"; (** * Check if stri1 is greater than stri2. * @return TRUE if stri1 is greater than stri2, * FALSE otherwise. *) const func boolean: (in string: stri1) > (in string: stri2) is action "STR_GT"; (** * Check if stri1 is less than or equal to stri2. * @return TRUE if stri1 is less than or equal to stri2, * FALSE otherwise. *) const func boolean: (in string: stri1) <= (in string: stri2) is action "STR_LE"; (** * Check if stri1 is greater than or equal to stri2. * @return TRUE if stri1 is greater than or equal to stri2, * FALSE otherwise. *) const func boolean: (in string: stri1) >= (in string: stri2) is action "STR_GE"; (** * Compare two strings. * @return -1, 0 or 1 if the first argument is considered to be * respectively less than, equal to, or greater than the * second. *) const func integer: compare (in string: stri1, in string: stri2) is action "STR_CMP"; (** * Compute the hash value of a ''string''. * @return the hash value. *) const func integer: hashCode (in string: stri) is action "STR_HASHCODE"; (** * Determine the length of a ''string''. * @return the length of the ''string''. *) const func integer: length (in string: stri) is action "STR_LNG"; (** * Determine if a string starts with a ''prefix''. * startsWith("tmp_s7c.c", "tmp_") returns TRUE * startsWith("example", "E") returns FALSE * @return TRUE if ''stri'' starts with ''prefix'', * FALSE otherwise. *) const func boolean: startsWith (in string: stri, in string: prefix) is return stri[.. length(prefix)] = prefix; (** * Determine if a string ends with a ''suffix''. * endsWith("hello.sd7", ".sd7") returns TRUE * endsWith("A string", "\0;") returns FALSE * @return TRUE if ''stri'' ends with ''suffix'', * FALSE otherwise. *) const func boolean: endsWith (in string: stri, in string: suffix) is return length(stri) >= length(suffix) and stri[succ(length(stri) - length(suffix)) ..] = suffix; (** * Check if ''stri'' has the ''searched'' characters starting from ''index''. * equalAtIndex("The quick brown fox", "quick", 5) returns TRUE * equalAtIndex("axis", "xi", 3) returns FALSE * @return TRUE if ''stri'' has the ''searched'' characters starting from ''index'', * FALSE otherwise. *) const func boolean: equalAtIndex (in string: stri, in string: searched, in integer: index) is return stri[index len length(searched)] = searched; (** * Determine leftmost position of string ''searched'' in ''mainStri''. * If the string is found the position of its first character * is the result. The first character in a string has the position 1. * pos("ABCDE ABCDE", "BC") returns 2 * pos("XYZXYZ", "ZYX") returns 0 * pos("123456789", "") returns 0 * @return the position of ''searched'' or 0 if ''mainStri'' * does not contain ''searched''. *) const func integer: pos (in string: mainStri, in string: searched) is action "STR_POS"; (** * Determine leftmost position of char ''searched'' in ''mainStri''. * The first character in a string has the position 1. * pos("ABCABC", 'B') returns 2 * pos("XYZ", 'A') returns 0 * @return the position of ''searched'' or 0 if ''mainStri'' * does not contain ''searched''. *) const func integer: pos (in string: mainStri, in char: searched) is action "STR_CHPOS"; (** * Search string ''searched'' in ''mainStri'' at or after ''fromIndex''. * The search starts at ''fromIndex'' and proceeds to the right. * If the string is found the position of its first character * is the result. The first character in a string has the position 1. * The pos function is designed to allow loops like: * * index := pos(stri, searchedStri); * while index <> 0 do * # Do something with index * index := pos(stri, searchedStri, succ(index)); * end while; * * @return the position of ''searched'' or 0 if ''mainStri'' * does not contain ''searched'' at or after ''fromIndex''. * @exception RANGE_ERROR If ''fromIndex'' <= 0 holds. *) const func integer: pos (in string: mainStri, in string: searched, in integer: fromIndex) is action "STR_IPOS"; (** * Search char ''searched'' in ''mainStri'' at or after ''fromIndex''. * The search starts at ''fromIndex'' and proceeds to the right. * The first character in a string has the position 1. * @return the position of ''searched'' or 0 if ''mainStri'' * does not contain ''searched'' at or after ''fromIndex''. * @exception RANGE_ERROR If ''fromIndex'' <= 0 holds. *) const func integer: pos (in string: mainStri, in char: searched, in integer: fromIndex) is action "STR_CHIPOS"; (** * Determine rightmost position of string ''searched'' in ''mainStri''. * If the string is found the position of its first character * is the result. The first character in a string has the position 1. * rpos("ABCDE ABCDE", "BC") returns 8 * rpos("XYZXYZ", "ZYX") returns 0 * rpos("123456789", "") returns 0 * @return the position of ''searched'' or 0 if ''mainStri'' * does not contain ''searched''. *) const func integer: rpos (in string: mainStri, in string: searched) is action "STR_RPOS"; (** * Determine rightmost position of char ''searched'' in ''mainStri''. * The first character in a string has the position 1. * rpos("ABCABC", 'B') returns 5 * rpos("XYZ", 'A') returns 0 * @return the position of ''searched'' or 0 if ''mainStri'' * does not contain ''searched''. *) const func integer: rpos (in string: mainStri, in char: searched) is action "STR_RCHPOS"; (** * Search string ''searched'' in ''mainStri'' at or before ''fromIndex''. * The search starts at ''fromIndex'' and proceeds to the left. * The first character in a string has the position 1. * The rpos function is designed to allow loops like: * * index := rpos(stri, searchedStri); * while index <> 0 do * # Do something with index * index := rpos(stri, searchedStri, pred(index)); * end while; * * @return the position of ''searched'' or 0 if ''mainStri'' * does not contain ''searched'' at or before ''fromIndex''. * @exception RANGE_ERROR If ''fromIndex'' > length(stri) holds. *) const func integer: rpos (in string: mainStri, in string: searched, in integer: fromIndex) is action "STR_RIPOS"; (** * Search char ''searched'' in ''mainStri'' at or before ''fromIndex''. * The search starts at ''fromIndex'' and proceeds to the left. * The first character in a string has the position 1. * @return the position of ''searched'' or 0 if ''mainStri'' * does not contain ''searched'' at or before ''fromIndex''. * @exception RANGE_ERROR If ''fromIndex'' > length(stri) holds. *) const func integer: rpos (in string: mainStri, in char: searched, in integer: fromIndex) is action "STR_RCHIPOS"; (* const proc: count (in string: mainStri, in string: searched) is action "STR_CNT"; *) (** * Replace occurrences of ''searched'' in ''mainStri'' by ''replacement''. * The function processes ''mainStri'' from left to right and replaces * ''searched'' by ''replacement''. After a ''searched'' has been replaced * the search for the next ''searched'' starts after the ''replacement''. * If a replacement creates new occurrences of ''searched'' they are left * intact. * replace("old gold", "old", "one") returns "one gone" * replace("it is very low", " ", " ") returns "it is very low" * replace("balll", "all", "al") returns "ball" * replace("faaaaceeees", "aacee", "ace") returns "faaaceees" * @return the result of the replacement. *) const func string: replace (in string: mainStri, in string: searched, in string: replacement) is action "STR_REPL"; (** * Replace one occurrence of ''searched'' in ''mainStri'' by ''replacement''. * If there is no occurrence of ''searched'' return the unchanged ''mainStri''. * @return the result of the replacement. *) const func string: replace1 (in string: mainStri, in string: searched, in string: replacement) is func result var string: replaced is ""; local var integer: pos is 0; begin pos := pos(mainStri, searched); if pos <> 0 then replaced := mainStri[.. pred(pos)] & replacement & mainStri[pos + length(searched) ..]; else replaced := mainStri; end if; end func; (** * Replace all occurrences of ''searched'' in ''mainStri'' by ''replacement''. * The function processes ''mainStri'' from left to right and replaces * ''searched'' by ''replacement''. If a replacement creates new occurrences * of ''searched'' they are replaced also. This can be used to replace * multiple occurrences of a character by one occurrence * replaceN("//path///file", "//", "/") returns "/path/file" * replaceN("it is very low", " ", " ") returns "it is very low" * replaceN("balll", "all", "al") returns "bal" * replaceN("faaaaceeees", "aacee", "ace") returns "faces" * @return the result of the replacement. * @exception MEMORY_ERROR If ''searched'' is a substring of ''replacement''. *) const func string: replaceN (in string: mainStri, in string: searched, in string: replacement) is func result var string: replaced is ""; local var integer: length is 0; begin if length(replacement) < length(searched) then length := length(mainStri); replaced := replace(mainStri, searched, replacement); while length(replaced) <> length do length := length(replaced); replaced := replace(replaced, searched, replacement); end while; elsif length(replacement) > length(searched) then if pos(replacement, searched) <> 0 then if pos(mainStri, searched) <> 0 then raise MEMORY_ERROR; else replaced := mainStri; end if; else length := length(mainStri); replaced := replace(mainStri, searched, replacement); while length(replaced) <> length do length := length(replaced); replaced := replace(replaced, searched, replacement); end while; end if; elsif searched = replacement then replaced := mainStri; else replaced := replace(mainStri, searched, replacement); while pos(replaced, searched) <> 0 do replaced := replace(replaced, searched, replacement); end while; end if; end func; (** * Replace occurrences of ''search1'' followed by ''search2'' with ''replacement''. * Searches ''mainStri'' for ''search1'' followed by ''search2''. The * characters from the beginning of ''search1'' to the end of ''search2'' * are replaced by ''replacement''. There can be zero or more characters * between ''search1'' and ''search2''. With ''replace2'' unnested comments * can be removed: * replace2("x := (*ord*) y;", "(*", "*)", "") returns "x := y;" * @return the result of the replacement. *) const func string: replace2 (in string: mainStri, in string: search1, in string: search2, in string: replacement) is func result var string: replaced is ""; local var integer: startPos is 0; var integer: endPos is 1; begin startPos := pos(mainStri, search1); while startPos <> 0 do replaced &:= mainStri[endPos .. pred(startPos)]; endPos := pos(mainStri, search2, startPos + length(search1)); if endPos <> 0 then replaced &:= replacement; endPos +:= length(search2); startPos := pos(mainStri, search1, endPos); else endPos := startPos; startPos := 0; end if; end while; replaced &:= mainStri[endPos ..]; end func; (** * Convert a string to upper case. * The conversion uses the default Unicode case mapping, * where each character is considered in isolation. * Characters without case mapping are left unchanged. * The mapping is independent from the locale. Individual * character case mappings cannot be reversed, because some * characters have multiple characters that map to them. * @return the string converted to upper case. *) const func string: upper (in string: stri) is action "STR_UP"; (** * Convert a string to lower case. * The conversion uses the default Unicode case mapping, * where each character is considered in isolation. * Characters without case mapping are left unchanged. * The mapping is independent from the locale. Individual * character case mappings cannot be reversed, because some * characters have multiple characters that map to them. * @return the string converted to lower case. *) const func string: lower (in string: stri) is action "STR_LOW"; (** * Number of screen columns occupied by the Unicode string ''stri''. * The width of single characters can be 0,1 or 2 depending on the * width occupied on a terminal. Non-spacing characters and control * characters have width of 0. Kanji and other full width characters * have a width of 2. * @return the sum of the character widths in ''stri''. *) const func integer: width (in string: stri) is forward; (** * Reverse the characters in a string. * reverse("ABC") returns "CBA" * @return the reversed string. *) const func string: reverse (in string: stri) is forward; (** * Return string with leading and trailing whitespace omitted. * All characters less than or equal to ' ' (space) count as whitespace. * trim(" /n xyz /r") returns "xyz" * @return string with leading and trailing whitespace omitted. *) const func string: trim (in string: stri) is action "STR_TRIM"; (** * Return string with leading whitespace omitted. * All characters less than or equal to ' ' (space) count as whitespace. * ltrim(" /n xyz /r") returns "xyz /r" * @return string with leading whitespace omitted. *) const func string: ltrim (in string: stri) is action "STR_LTRIM"; (** * Return string with trailing whitespace omitted. * All characters less than or equal to ' ' (space) count as whitespace. * rtrim(" /n xyz /r") returns " /n xyz" * @return string with trailing whitespace omitted. *) const func string: rtrim (in string: stri) is action "STR_RTRIM"; (** * Trim a string such that it can be converted to ''aType''. * Removes leading and trailing whitespace from ''stri''. * This function is overloaded for types where removing leading * or trailing whitespace would change the value. * trimValue(integer, " 1 ") returns "1" * integer(trimValue(integer, " 1 ")) returns 1 * integer parse trimValue(integer, " 1 ") returns 1 * @return the trimmed string. *) const func string: trimValue (in type: aType, in string: stri) is return trim(stri); (** * Trim a string such that it can be converted to ''string''. * Leaves ''stri'' unchanged, since trimming would change the value. * trimValue(string, " 1 ") returns " 1 " * string(trimValue(string, " 1 ")) returns " 1 " * string parse trimValue(string, " 1 ") returns " 1 " * @return the unchanged string. *) const func string: trimValue (attr string, in string: stri) is return stri; (** * Convert to a string. * @return its parameter unchanged. *) const func string: str (in string: stri) is action "STR_STR"; const func string: literal (in string: stri) is action "STR_LIT"; const func string: c_literal (in string: stri) is action "STR_CLIT"; const func string: getint (inout string: stri) is func result var string: digits is ""; local var integer: leng is 0; var integer: pos is 1; begin leng := length(stri); while pos <= leng and stri[pos] >= '0' and stri[pos] <= '9' do incr(pos); end while; digits := stri[.. pred(pos)]; stri := stri[pos ..]; end func; const func string: gets (inout string: stri, in integer: maxLength) is func result var string: striRead is ""; begin striRead := stri[.. maxLength]; stri := stri[succ(length(striRead)) ..]; end func; (** * Convert to a string. * @return its parameter unchanged. *) const func string: string (in string: stri) is return stri; (** * Convert to a string. * @return its parameter unchanged. *) const func string: (attr string) parse (in string: stri) is return stri; DECLARE_TERNARY(string);