/* * Copyright 2017 Magnus Madsen, Stephen Tetley * * Use of this source code is governed by the Apache 2.0 license * that can be found in the LICENSE.md file. */////// Type of immutable strings, and its functions.///pubmod String {import java.lang.Systemimport java.lang.{String => JString}import java.nio.charset.StandardCharsetsimport java.util.regex.Patternimport java.util.regex.PatternSyntaxExceptionimport java.lang.IndexOutOfBoundsExceptionuse Regex.Flag////// Returns the character at position `i` in the string `s`.////// Throws `StringIndexOutOfBoundsException` if the index is out of bounds.///pubdefget(i: Int32, s: String): Char =charAt(i, s)////// Returns the character at position `i` in the string `s`.///pubdefcharAt(i: Int32, s: String): Char = s.charAt(i)////// Optionally return the character at position `i` in the string `s`.///pubdefnth(i: Int32, s: String): Option[Char] =if (0<=iandi<length(s)) Some(charAt(i, s))else None////// Returns the code point at position `i` in the string `s`.///pubdefcodePointAt(i: Int32, s: String): Int32 = s.codePointAt(i)////// Optionally returns the code point at position `i` in the string `s`.///pubdefnthCodePoint(i: Int32, s: String): Option[Int32] =if (0<=iandi<length(s)) Some(codePointAt(i, s))else None////// Returns the string `s1` followed by the string `s2`.///pubdefconcat(s1: String, s2: String): String =s1.concat(s2)////// Returns `true` if the string `s` is the empty string.///pubdefisEmpty(s: String): Bool =s.isEmpty()////// Returns `true` if the string `s` is non-empty.///pubdefnonEmpty(s: String): Bool = notisEmpty(s)////// Returns the number of characters in the string `s`.///pubdeflength(s: String): Int32 =s.length()////// Returns the number of characters in the string `s`.///pubdefsize(s: String): Int32 = length(s)////// Returns `true` if the string `s` starts with the string `prefix`.///pubdefstartsWith(prefix: {prefix = String}, s: String): Bool =s.startsWith(prefix#prefix)////// Returns `true` if the string `s` ends with the string `suffix`.///pubdefendsWith(suffix: {suffix = String}, s: String): Bool =s.endsWith(suffix#suffix)////// Splits the string `s` around matches of the regular expression `regex`.///pubdefsplit(regex: {regex = String}, s: String): List[String] =unsafeIO { Array.toList(s.split(regex#regex)) }////// Returns the lower case version of the string `s`.///pubdeftoLowerCase(s: String): String =s.toLowerCase()////// Returns the upper case version of the string `s`.///pubdeftoUpperCase(s: String): String =s.toUpperCase()////// Returns the given string `s` as a list of characters.///pubdeftoList(s: String): List[Char] = List.map(i -> charAt(i, s), List.range(0, length(s)))////// Returns a copy of the string `s` without trailing and leading whitespaces.////// Returns a new empty string if there is no characters in `s`.///pubdeftrim(s: String): String =s.trim()////// Get the system line separator.///pubdeflineSeparator(): String = System.lineSeparator()////// Returns the given string `s` as an array of characters.///pubdeftoArray(rc: Region[r], s: String): Array[Char, r] \ r =Array.init(rc, i -> charAt(i, s), length(s))////// Returns the given string `s` as a vector of characters.///pubdeftoVector(s: String): Vector[Char] = regionrc {letarr = toArray(rc, s);Array.toVector(arr) }////// Returns the given string `s` as a vector of UTF-8 bytes.///pubdeftoBytes(s: String): Vector[Int8] =unsafeIO { Array.toVector(s.getBytes(StandardCharsets.UTF_8)) }////// Returns a string from the given vector of UTF-8 bytes `v`.///pubdeffromBytes(v: Vector[Int8]): String = regionrc {letarr = Vector.toArray(rc, v);unsafeIO { new JString(arr, StandardCharsets.UTF_8) } }////// Compile the regular expression `regex` into a Regex object (internally/// a Regex is `java.util.regex.Pattern`).////// Returns `Err` if the regex is ill-formed and cannot be compiled.////// The syntax of regular expressions is the same as Java, see the JDK Javadocs/// of the class `util.regex.Pattern` for reference.////// Warning: It is strongly recommended to use a Regex literal instead of `toRegex`./// Regex literals are checked at compile time.///pubdeftoRegex(regex: String): Result[String, Regex] =try { Ok(Pattern.compile(regex)) } catch {casee: PatternSyntaxException => Err(unsafeIO { e.getMessage() }) }////// Compile the regular expression `regex` into a Regex with the supplied flags.////// Returns `Err` if the regex is ill-formed and cannot be compiled.////// Warning: It is strongly recommended to use a Regex literal instead of `toRegexWithFlags`./// Regex literals are checked at compile time. See the JDK documentation of/// `java.util.regex.Pattern` for guidance on enabling flags with embedded flag expressions.///pubdeftoRegexWithFlags(flags: Set[Flag], regex: String): Result[String, Regex] =try { Ok(Pattern.compile(regex, Regex.sumFlags(flags))) } catch {casee: PatternSyntaxException => Err(unsafeIO { e.getMessage() }) }////// Build a string of length `len` by applying `f` to the successive indices.///pubdefinit(f: Int32 -> Char \ ef, len: Int32): String \ ef =if (len<=0) ""elseinitHelper(f, len)////// Helper function for `init`.////// Assumes that `len > 0`.///definitHelper(f: Int32 -> Char \ ef, len: Int32): String \ ef = regionrc {letsb = StringBuilder.empty(rc);defloop(i) = {if (i>=len)StringBuilder.toString(sb)else {letc = f(i);StringBuilder.append(c, sb);loop(i+1) } };loop(0) }////// Concatenate a list of strings into a single string.///pubdefflatten(xs: List[String]): String = regionrc {letsb = StringBuilder.empty(rc);List.forEach(x -> StringBuilder.appendString(x, sb), xs);StringBuilder.toString(sb) }////// Concatenate a list of strings into a single string, inserting the separator `sep` between/// each pair of strings.///pubdefintercalate(sep: String, xs: List[String]): String = regionrc {letsb = StringBuilder.empty(rc);letarr = List.toArray(rc, xs);StringBuilder.intercalate(sep, arr, sb);StringBuilder.toString(sb) }////// Concatenate a list of strings into a single string, inserting the separator `sep` between/// each pair of strings.///pubdefintercalateChar(sep: Char, xs: List[String]): String = matchxs {case Nil => ""casex :: rs => regionrc {letsb = StringBuilder.empty(rc);StringBuilder.appendString(x, sb);letappendStep = s -> { StringBuilder.append(sep, sb); StringBuilder.appendString(s, sb) };List.forEach(appendStep, rs);StringBuilder.toString(sb) } }////// Returns the result of applying `f` to every character in `s`.///pubdefmap(f: Char -> Char \ ef, s: String): String \ ef =letlen = length(s);init(i -> f(charAt(i, s)), len)////// Returns the result of applying `f` to every character in `s` along with that character's index.///pubdefmapWithIndex(f: (Int32, Char) -> Char \ ef, s: String): String \ ef =letlen = length(s);init(i -> f(i, charAt(i, s)), len)////// Returns the reverse of `s`.///pubdefreverse(s: String): String =letstart = length(s) -1;init(i -> charAt(start-i, s), start+1)////// Rotate the contents of string `s` by `n` steps to the left.///pubdefrotateLeft(n: Int32, s: String): String =if (length(s) <1)""elseif (n<0)rotateRightHelper(Int32.abs(n), s)elserotateLeftHelper(n, s)////// Helper function for `rotateLeft`, `n` must be positive.///defrotateLeftHelper(n: Int32, s: String): String =letlen = length(s);letf = i -> { leti1 = n+i; charAt(i1`Int32.remainder`len, s) };init(f, len)////// Rotate the contents of string `s` by `n` steps to the right.///pubdefrotateRight(n: Int32, s: String): String =if (length(s) <1)""elseif (n<0)rotateLeftHelper(Int32.abs(n), s)elserotateRightHelper(n, s)////// Helper function for `rotateRight`, `n` must be positive.///defrotateRightHelper(n: Int32, s: String): String =letlen = length(s);letn1 = n`Int32.remainder`len;letstart = len-n1;letf = i -> { leti1 = start+i; charAt(i1`Int32.remainder`len, s) };init(f, len)////// Returns `true` if the entire string `s` is matched by the regular expression `regex`.////// Note - use `isSubmatch` to search for a substring.///pubdefisMatch(regex: {regex = String}, s: String): Bool =try {s.matches(regex#regex) } catch {case_: PatternSyntaxException => false }////// Returns `true` if the string `s` is matched by the regular expression `regex` at any point.///pubdefisSubmatch(regex: {regex = String}, s: String): Bool =isSubmatchHelper(regex#regex, s)////// Helper function for `isSubmatch`.///defisSubmatchHelper(pattern: String, s: String): Bool =try {letp1 = Pattern.compile(pattern);letm1 = p1.matcher(s);unsafeIO { m1.find(0) } } catch {case_: PatternSyntaxException => false }////// Returns the substring of `s` from index `b` (inclusive) to index `e` (exclusive).////// If `b` or `e` are out-of-bounds, return the empty string.///pubdefslice(start: {start = Int32}, end: {end = Int32}, s: String): String = try {s.substring(start#start, end#end) } catch {case_: IndexOutOfBoundsException => "" }////// Get the substring of `s` to the left of index `end` (exclusive).////// `sliceLeft == slice(0 , e, s)`///pubdefsliceLeft(end: {end = Int32}, s: String): String = matchlength(s) {caselenifend#end >len => ""case_ifend#end <0 => ""case_ => slice(start = 0, end, s) }////// Get the substring of `s` to the right starting at index `start` (inclusive).////// `sliceRight == slice(start , length(s), s)`///pubdefsliceRight(start: {start = Int32}, s: String): String = matchlength(s) {caselenifstart#start >=len => ""case_ifstart#start <0 => ""caselen => slice(start, end = len, s) }////// Optionally returns the position of the first character in `x` satisfying `f`.////// Returns `None` if no character in `s` satisfies `f`.///pubdeffindIndexOfLeft(f: Char -> Bool \ ef, s: String): Option[Int32] \ ef =letlen = length(s);defloop(i) = {if (i>=len) -1elseif (f(charAt(i, s)))ielseloop(i+1) };leti = loop(0);if (i<0) None else Some(i)////// Optionally return the position of the first character in `s` satisfying `f`, reading right to left.////// If nothing satisfies `f` return None.///pubdeffindIndexOfRight(f: Char -> Bool, s: String): Option[Int32] =defloop(i) = {if (i<0) -1elseif (f(charAt(i, s)))ielseloop(i-1) };letend = length(s) -1;leti = loop(end);if (i<0) None else Some(i)////// Returns the positions of the all the elements in `s` satisfying `f`.///pubdeffindIndices(f: Char -> Bool \ ef, s: String): Vector[Int32] \ ef = regionrc {letlen = length(s);letl = MutList.empty(rc);defloop(i) = {if (i>=len)()else {if (f(charAt(i, s))) { MutList.push(i, l) };loop(i+1) } };loop(0);MutList.toVector(l) }////// Returns the positions of the all the occurrences of `substr` in `s`.////// Returns an empty vector if `substr` is the empty string.///pubdefindicesOf(substr: {substr = String}, s: String): Vector[Int32] =letsublen = length(substr#substr);letstep = ix -> matchindexOfLeftWithOffset({substr = substr#substr}, {offset = ix}, s) {case None => Nonecase Some(ix2) => Some((ix2, ix2+sublen)) };List.unfold(step, 0) |> List.toVector////// Alias for `takeLeft`.///pubdeftake(n: Int32, s: String): String = takeLeft(n, s)////// Take the first `n` characters of string `s` from the left.////// If `n` extends past the end of string `s`, return all the characters/// of `s`.///pubdeftakeLeft(n: Int32, s: String): String =if (n>=length(s)) selseslice(start = 0, end = n, s)////// Take the last `n` characters of string `s` from the right.////// If `n` is greater than the length of string `s`, return all the characters/// of `s`.///pubdeftakeRight(n: Int32, s: String): String =letlen = length(s);if (n>=len) selseslice(start = len-n, end = len, s)////// Alias for `dropLeft`.///pubdefdrop(n: Int32, s: String): String = dropLeft(n, s)////// Drop the first `n` characters of string `s` from the left.////// If `n` extends past the end of string s, return the empty string.///pubdefdropLeft(n: Int32, s: String): String =if (n<=0)selseslice(start = n, end = length(s), s)////// Drop the last `n` characters of string `s` from the right.////// If `n` is greater than the length of string `s`, return the empty string.///pubdefdropRight(n: Int32, s: String): String =if (n>0)letlen = length(s);slice(start = 0, end = len-n, s)elses////// Alias for `takeWhileLeft`.///pubdeftakeWhile(f: Char -> Bool, s: String): String = takeWhileLeft(f, s)////// Returns the initial prefix of string `s` where all the chars satisfy/// the predicate `f`.////// The function `f` must be pure.///pubdeftakeWhileLeft(f: Char -> Bool, s: String): String =matchfindIndexOfLeft(x -> not (f(x)), s) {case None => scase Some(i) => take(i, s) }////// Returns the suffix of string `s` where all the characters satisfy/// the predicate `f`.////// The function `f` must be pure.///pubdeftakeWhileRight(f: Char -> Bool, s: String): String =matchfindIndexOfRight(x -> not (f(x)), s) {case None => scase Some(i) => slice(start = i+1, end = length(s), s) }////// Alias for `dropWhileLeft`.///pubdefdropWhile(f: Char -> Bool, s: String): String = dropWhileLeft(f, s)////// Returns the tail of string `s` after dropping all the initial chars/// that satisfy the predicate `f`.////// The function `f` must be pure.///pubdefdropWhileLeft(f: Char -> Bool, s: String): String =matchfindIndexOfLeft(x -> not (f(x)), s) {case None => ""case Some(i) => sliceRight(start = i, s) }////// Returns the front of string `s` after dropping all the characters/// from the right end that satisfy the predicate `f`.////// The function `f` must be pure.///pubdefdropWhileRight(f: Char -> Bool, s: String): String =matchfindIndexOfRight(x -> not (f(x)), s) {case None => ""case Some(i) => sliceLeft(end = i+1, s) // must include i }////// Returns the middle of string `s` after dropping all the characters/// from both ends that satisfy the predicate `f`.////// The function `f` must be pure.///pubdefdropWhileAround(f: Char -> Bool, s: String): String =match (findIndexOfLeft(x -> not (f(x)), s), findIndexOfRight(x -> not (f(x)), s)) {case (None, None) => ""case (Some(l), None) => sliceRight(start = l, s)case (None, Some(r)) => sliceLeft(end = r, s)case (Some(l), Some(r)) => slice(start = l, end = r+1, s) }////// Split the string `s` at the position `n` returning the left and/// right (inclusive `n`) parts.////// If `n` exceeds the length of string `s`, return the whole string/// paired with the empty string. Symmetrically for `n` less than 0.///pubdefsplitAt(n: Int32, s: String): (String, String) =matchlength(s) {case_ifn<=0 => ("", s)caselenifn>len => (s, "")case_ => (sliceLeft(end = n, s), sliceRight(start = n, s)) }////// Applies `f` to a start value `x` and all elements in `s` going from left to right.///pubdeffoldLeft(f: (b, Char) -> b \ ef, x: b, s: String): b \ ef =letlen = length(s);defloop(acc, i) = {if (i>=len)accelseletacc1 = f(acc, charAt(i, s));loop(acc1, i+1) };loop(x, 0)////// Applies `f` to a start value `x and all elements in `s` going from right to left.///pubdeffoldRight(f: (Char, b) -> b \ ef, x: b, s: String): b \ ef =defloop(acc, i) = {if (i<0)accelseletacc1 = f(charAt(i, s), acc);loop(acc1, i-1) };letend = length(s) -1;loop(x, end)////// Accumulates the result of applying `f` pairwise to the elements of `s` and `t`/// starting with the initial value `x` and going from left to right.///pubdeffoldLeft2(f: (b, Char, Char) -> b \ ef, x: b, s: String, t: String): b \ ef =letlen = Int32.min(length(s), length(t));defloop(acc, i) = {if (i>=len)accelseletacc1 = f(acc, charAt(i, s), charAt(i, t));loop(acc1, i+1) };loop(x, 0)////// Accumulates the result of applying `f` pairwise to the elements of `s` and `t`/// starting with the initial value `x` and going from right to left.///pubdeffoldRight2(f: (Char, Char, b) -> b \ ef, x: b, s: String, t: String): b \ ef =letsn = length(s) -1;lettn = length(t) -1;defloop(acc, i, j) = {if (i<0orj<0)accelseletacc1 = f(charAt(i, s), charAt(j, t), acc);loop(acc1, i-1, j-1) };loop(x, sn, tn)////// Build a string from the seed value `x` applying the function `f` until `f` returns `None`.///pubdefunfold(f: b -> Option[(Char, b)] \ ef, x: b): String \ ef = regionrc {letsb = StringBuilder.empty(rc);defloop(a) = {leta1 = f(a);matcha1 {case None => StringBuilder.toString(sb)case Some((c, st1)) => {StringBuilder.append(c, sb);loop(st1) } } };loop(x) }////// Build a string by applying the function `next` to `()`. `next` is expected to encapsulate/// a stateful resource such as a file handle that can be iterated.////// `next` should return `Some(c)` to signal a new char `c`.////// `next` should return `None` to signal the end of building the string.///pubdefunfoldWithIter(f: Unit -> Option[Char] \ ef): String \ ef = regionrc {letsb = StringBuilder.empty(rc);defloop() = {matchf() {case None => StringBuilder.toString(sb)case Some(c) => {StringBuilder.append(c, sb);loop() } } };loop() }////// Build a string from the seed value `x` applying the function `f` until `f` returns `None`.////// This is a version of `unfold` where `f` generates substrings rather than chars.///pubdefunfoldString(f: b -> Option[(String, b)] \ ef, x: b): String \ ef = regionrc {letsb = StringBuilder.empty(rc);defloop(a) = {leta1 = f(a);matcha1 {case None => StringBuilder.toString(sb)case Some(((s, st1))) => {StringBuilder.appendString(s, sb);loop(st1) } } };loop(x) }////// Build a string by applying the function `next` to `()`. `next` is expected to encapsulate/// a stateful resource such as a file handle that can be iterated.////// `next` should return `Some(s)` to signal a new substring `s`.////// `next` should return `None` to signal the end of building the string.///pubdefunfoldStringWithIter(f: Unit -> Option[String] \ ef): String \ ef = regionrc {letsb = StringBuilder.empty(rc);defloop() = {matchf() {case None => StringBuilder.toString(sb)case Some(s) => {StringBuilder.appendString(s, sb);loop() } } };loop() }////// Build a string by applying `f` to the initial char `x`.////// `f` should return `Some(c1)` to signal a new char `c1` (which also becomes the next input to `f`).////// `f` should return `None` to signal the end of building the string.///pubdefiterate(f: Char -> Option[Char] \ ef, x: Char): String \ ef = regionrc {letsb = StringBuilder.empty(rc);defloop(c) = {matchf(c) {case None => StringBuilder.toString(sb)case Some(c1) => {StringBuilder.append(c1, sb);loop(c1) } } };loop(x) }////// Returns `true` if and only if at least one char in `s` satisfies the predicate `f`.////// Returns `false` if `s` is empty.///pubdefexists(f: Char -> Bool \ ef, s: String): Bool \ ef = matchfindIndexOfLeft(f, s) {case None => falsecase Some(_) => true }////// Returns `true` if and only if all chars in `s` satisfy the predicate `f`.////// Returns `true` if `s` is empty.///pubdefforAll(f: Char -> Bool \ ef, s: String): Bool \ ef = matchfindIndexOfLeft(x -> not (f(x)), s) {case None => truecase Some(_) => false }////// Returns `true` if and only if all chars in `s` are ascii characters.////// Returns `true` if `s` is empty.///pubdefisAscii(s: String): Bool =forAll(Char.isAscii, s)////// Returns `true` if and only if all chars in `s` are white space characters.////// Returns `true` if `s` is empty.///pubdefisWhitespace(s: String): Bool =forAll(Char.isWhitespace, s)////// Returns string `s` with all leading space characters removed.///pubdeftrimLeft(s: String): String = dropWhileLeft(Char.isWhitespace, s)////// Returns string `s` with all trailing space characters removed.///pubdeftrimRight(s: String): String = dropWhileRight(Char.isWhitespace, s)////// Returns a string with the string `s` repeated `n` times.////// Returns the empty string if `n < 0`.///pubdefrepeat(n: Int32, s: String): String =if (n<0)""elses.repeat(n)////// Pad the string `s` at the left with the supplied char `c` to fit the width `w`.///pubdefpadLeft(w: Int32, c: Char, s: String): String =letlen = length(s);if (len>=w)selserepeat(w-len, Char.toString(c)) +s////// Pad the string `s` at the right with the supplied char `c` to fit the width `w`.///pubdefpadRight(w: Int32, c: Char, s: String): String =letlen = length(s);if (len>=w)selses+repeat(w-len, Char.toString(c))////// Center the string `s` by padding with the supplied char `c` on both sides to fit the width `w`.////// If the length of the string exceeds the given width, the string will be returned unchanged.////// Requiring an uneven number of spaces, the string will be left biased,/// e.g `String.center(4, '_', "a") -> "_a__"`.///pubdefcenter(w: Int32, c: Char, s: String): String = regionrc {letspace = w-length(s);letfloor = space/2;letceil = space/2+Int32.remainder(space, 2);letprefix = repeat(floor, Char.toString(c));letpostfix = repeat(ceil, Char.toString(c));letsb = StringBuilder.empty(rc);StringBuilder.appendString(prefix, sb);StringBuilder.appendString(s, sb);StringBuilder.appendString(postfix, sb);StringBuilder.toString(sb) }////// Returns `s` with every match of the substring `src` replaced by the string `dst`.///pubdefreplace(src: {src = String}, dst: {dst = String}, s: String): String =s.replace(src#src, dst#dst)////// Returns `s` with every match of the character `src` replaced by the character `dst`.///pubdefreplaceChar(src: {src = Char}, dst: {dst = Char}, s: String): String =s.replace(src#src, dst#dst)////// Returns `s` with every match of the regular expression `regex` replaced by the string `to`.///pubdefreplaceMatches(regex: {regex = String}, to: {to = String}, s: String): String =s.replaceAll(regex#regex, to#to)////// Returns `s` with the first match of the regular expression `regex` replaced by the string `to`.///pubdefreplaceFirstMatch(regex: {regex = String}, to: {to = String}, s: String): String =s.replaceFirst(regex#regex, to#to)////// Returns `s` with the element at index `i` replaced by `a`.////// Returns `s` if `i < 0` or `i > length(xs)-1`.///pubdefupdate(i: Int32, a: Char, s: String): String =letf = ix -> if (ix==i) aelsecharAt(ix, s);init(f, length(s))////// Returns `s` with the `n` elements starting at index `i` replaced with the elements of `sub`.////// If any of the indices `i, i+1, i+2, ... , i+n-1` are out of range in `s` then no patching is done at these indices./// If `s` becomes depleted then no further patching is done./// If patching occurs at index `i+j` in `s`, then the element at index `j` in `sub` is used.///pubdefpatch(i: Int32, n: Int32, sub: String, s: String): String =letsublen = length(sub);letf = ix ->if (ix>=iandix<i+n) {letoffset = ix-i;if (offset<0oroffset>=sublen) charAt(ix, s) elsecharAt(offset, sub) } elsecharAt(ix, s);init(f, length(s))////// Indent every line in string `s` by `n` spaces. `n` must be greater than `0`.////// If the string `s` in nonempty, the returned string normalizes line/// termination characters and adds a line terminator to the last line/// of the string if it does not already end with a newline.////// If the string `s` is empty, then the empty string is returned.///pubdefindent(n: Int32, s: String): String =if (n<=0orlength(s) ==0)selseindentHelper(n, s)////// Helper function for `indent`.///defindentHelper(n: Int32, s: String): String = regionrc {letsb = StringBuilder.empty(rc);letprefix = repeat(n, " ");letstep = x -> {StringBuilder.appendString(prefix, sb);StringBuilder.appendLine(x, sb) };List.forEach(step, lines(s));StringBuilder.toString(sb) }////// Strip every indented line in string `s` by `n` spaces. `n` must be greater than `0`./// Note, tabs are counted as a single space.////// If the string `s` in nonempty, the returned string normalizes line/// termination characters and adds a line terminator to the last line/// of the string if it does not already end with a newline.////// If the string `s` is empty, then the empty string is returned.///pubdefstripIndent(n: Int32, s: String): String =if (n<=0orlength(s) ==0)selsestripIndentHelper(n, s)////// Helper function for `stripIndent`.///defstripIndentHelper(n: Int32, s: String): String = regionrc {letsb = StringBuilder.empty(rc);letlimit = Int32.min(n, length(s));defloop(s1, ix) = {if (ix>=limit)sliceRight(start = ix, s1)elseif (notChar.isWhitespace(charAt(ix, s1)))sliceRight(start = ix, s1)elseloop(s1, ix+1) };letstep = s1 -> {letline = loop(s1, 0);StringBuilder.appendLine(line, sb) };List.forEach(step, lines(s));StringBuilder.toString(sb) }////// Split the string `s` into an array of lines, breaking on newline.////// Newline is recognized as any Unicode linebreak sequence including/// Windows (carriage return, line feed) or Unix (line feed).///pubdeflines(s: String): List[String] =if (isEmpty(s)) Nilelsesplit({regex = "\\R"}, s)////// Join the list of strings `a` separating each pair of strings and/// ending the result string with the system dependent line separator.///pubdefunlines(a: List[String]): String = regionrc {letsb = StringBuilder.empty(rc);List.forEach(x -> StringBuilder.appendLine(x, sb), a);StringBuilder.toString(sb) }////// Split the string `s` into an list of words, dividing on one or more white space characters./// Leading and trailing spaces are trimmed.///pubdefwords(s: String): List[String] = matchtrim(s) {cases1ifisEmpty(s1) => Nilcases1 => split({regex = "\\s+"}, s1) }////// Join the array of strings `l` separating each pair of strings with a/// single space character.///pubdefunwords(l: List[String]): String = matchl {case Nil => ""casex :: xs => regionrc {letsb = StringBuilder.empty(rc);StringBuilder.appendString(x, sb);letf = s -> { StringBuilder.append(' ', sb); StringBuilder.appendString(s, sb) };List.forEach(f, xs);StringBuilder.toString(sb) } }////// Alias for `indexOfLeft`.///pubdefindexOf(substr: {substr = String}, s: String): Option[Int32] = indexOfLeft({substr = substr#substr}, s)////// Return the index of the first occurrence of `substr` in `s` from the left.////// If `substr` is not present in `s` return None.////// If `substr` is the empty string return None.///pubdefindexOfLeft(substr: {substr = String}, s: String): Option[Int32] =if (isEmpty(substr#substr)) Noneelse {leti = s.indexOf(substr#substr);if (i<0) None else Some(i) }////// Return the index of the first occurrence of `substr` in `s` from the right.////// If `substr` is not present in `s` return None.////// If `substr` is the empty string return None.///pubdefindexOfRight(substr: {substr = String}, s: String): Option[Int32] =if (isEmpty(substr#substr)) Noneelse {leti = s.lastIndexOf(substr#substr);if (i<0) None else Some(i) }////// Returns `true` if and only if `substr` is an infix of `s`.///pubdefcontains(substr: {substr = String}, s: String): Bool =if (isEmpty(substr#substr))trueelse {leta = indexOfLeft({substr = substr#substr}, s);notOption.isEmpty(a) }////// Return the common prefix of strings `s1` and `s2`.////// Returns the empty string if `s1` and `s2` do not share a common prefix.///pubdefcommonPrefix(s1: String, s2: String): String =letlen1 = length(s1);letlen2 = length(s2);defloop(ix) = {if (ix>=len1orix>=len2)takeLeft(ix, s1)else {letc1 = charAt(ix, s1);letc2 = charAt(ix, s2);if (c1!=c2)takeLeft(ix, s1)elseloop(ix+1) } };loop(0)////// Return the common suffix of strings `s1` and `s2`.////// Returns the empty string if `s1` and `s2` do not share a common suffix.///pubdefcommonSuffix(s1: String, s2: String): String =defloop(i, j, acc) = {if (i<0)s1elseif (j<0)s2else {letc1 = charAt(i, s1);letc2 = charAt(j, s2);if (c1!=c2)takeRight(acc, s1)elseloop(i-1, j-1, acc+1) } };letstart1 = length(s1) -1;letstart2 = length(s2) -1;loop(start1, start2, 0)////// Abbreviate the string `s` if it exceeds the width `w`.////// If the length of `s` exceeds `w` and `w >= 3` then `s` is truncated and the first/// three characters are replaced with ellipses.////// If the length of `s` exceeds `w` and `w < 3` then the empty string is returned.///pubdefabbreviateLeft(w: Int32, s: String): String =matchlength(s) {caseleniflen<w => scase_ifw<3 => ""caselen => {letstart = len-w+3;"..."+slice(start = start, end = len, s) } }////// Abbreviate the string `s` if it exceeds the width `w`.////// If the length of `s` exceeds `w` and `w >= 3` then `s` is truncated and the last/// three characters are replaced with ellipses.////// If the length of `s` exceeds `w` and `w < 3` then the empty string is returned.///pubdefabbreviateRight(w: Int32, s: String): String =matchlength(s) {caseleniflen<w => scase_ifw<3 => ""case_ => slice(start = 0, end = w-3, s) +"..." }////// Calculate the Levenshtein distance between the strings `s` and `t`.////// The answer is the number deletions, insertions or substitutions needed to turn/// string `s` into string `t`.///pubdeflevenshteinDistance(s: String, t: String): Int32 = regionrc {letm = length(s);letn = length(t);letv0 = Array.repeat(rc, n+1, 0);letv1 = Array.repeat(rc, n+1, 0);forIndex(0, n, i -> Array.put(i, i, v0));forIndex(0,m-1,i -> {Array.put(i+1, 0, v1);forIndex(0,n-1,j -> {letsubstitutionCost = if (charAt(i, s) ==charAt(j, t)) 0else1;leta = Array.get(j, v1) +1;letb = Array.get(j+1, v0) +1;letc = Array.get(j, v0) +substitutionCost;Array.put(Int32.min(a, Int32.min(b, c)), j+1, v1) } );arraySwap(v0, v1) } );Array.get(n, v0) }////// Helper function for `levenshteinDistance`.////// This implements a forloop, the end point `e` is inclusive.///defforIndex(b: Int32, e: Int32, f: Int32 -> Unit \ ef): Unit \ ef =if (b<=e) { f(b); forIndex(b+1, e, f) }////// Helper function for `levenshteinDistance`.///defarraySwap(a: Array[Int32, r1], b: Array[Int32, r2]): Unit \ { r2, r1 } =forIndex(0, Array.length(a) -1, { i -> Array.put(Array.get(i, b), i, a) })////// Returns an array where the element at index `i` is `(x, y)` where/// `x` is the element at index `i` in `a` and `y` is the element at index `i` in `b`.////// If either `a` or `b` becomes depleted, then no further elements are added to the resulting array.///pubdefzip(a: String, b: String): List[(Char, Char)] =letlen = Int32.min(length(a), length(b));List.unfold(i -> if (i>=len) None else Some(((charAt(i, a), charAt(i, b)), i+1)), 0)////// Returns a string where the element at index `i` is `f(x, y)` where/// `x` is the element at index `i` in `a` and `y` is the element at index `i` in `b`.////// If either `a` or `b` becomes depleted, then no further elements are added to the resulting array.///pubdefzipWith(f: (Char, Char) -> Char \ ef, a: String, b: String): String \ ef =letlen = Int32.min(length(a), length(b));init(i -> f(charAt(i, a), charAt(i, b)), len)////// Split the string `s` on matches of `substr`.///pubdefsplitOn(substr: {substr = String}, s: String): List[String] =letmaxlen = length(s);letsublen = length(substr#substr);letstep = ix ->// Note - to stop we have to be greater than maxlen.// We want to return a final empty string if `s` terminates with `substr`.if (ix>maxlen) Noneelse {matchindexOfLeftWithOffset({substr = substr#substr}, {offset = ix}, s) {case None => {lets1 = sliceRight(start = ix, s); Some((s1, maxlen+1)) }case Some(ix2) => {lets1 = slice(start = ix, end = ix2, s); Some((s1, ix2+sublen)) } } };List.unfold(step, 0)////// This is `indexOfLeft` with a start offset.////// Returns `None` if `substr` is the empty string.///pubdefindexOfLeftWithOffset(substr: {substr = String}, offset: {offset = Int32}, s: String): Option[Int32] =if (isEmpty(substr#substr)) Noneelse {letix = s.indexOf(substr#substr, offset#offset);if (ix<0) None else Some(ix) }////// This is `indexOfRight` with a start offset.////// Returns `None` if `substr` is the empty string.///pubdefindexOfRightWithOffset(substr: {substr = String}, offset: {offset = Int32}, s: String): Option[Int32] =if (isEmpty(substr#substr)) Noneelse {letix = s.lastIndexOf(substr#substr, offset#offset);if (ix<0) None else Some(ix) }////// Split the string `s` into chunks of length `k`, the last chunk may be smaller than `k`.////// `k` should be greater than 0.///pubdeftoChunks(k: Int32, s: String): List[String] =if (k<=0) NilelsetoChunksHelper(k, s)////// Helper function for `toChunks`.////// Precondition: size > 0///deftoChunksHelper(size: Int32, s: String): List[String] =letlen = length(s);letstep = pos -> matchpos {caseiifi>=len => Nonecaseiifi+size>=len => Some((sliceRight(start = i, s), len))casei => Some((slice(start = i, end = i+size, s), i+size)) };List.unfold(step, 0)////// Find the first instance of `substr` in string `s`, return a pair of the/// prefix of string `s` up to `substr` and the rest of string `s` including `substr`.///pubdefbreakOnLeft(substr: {substr = String}, s: String): (String, String) =matchindexOfLeft({substr = substr#substr}, s) {case None => (s, "")case Some(i) => (sliceLeft(end = i, s), sliceRight(start = i, s)) }////// Find the last instance of `substr` in string `s`, return a pair of the/// initial string including `substr` and suffix from `substr`.///pubdefbreakOnRight(substr: {substr = String}, s: String): (String, String) =letsublen = length(substr#substr);matchindexOfRight({substr = substr#substr}, s) {case None => (s, "")case Some(i) => (sliceLeft(end = i+sublen, s), sliceRight(start = i+sublen, s)) }////// Count the occurrences of `substr` in string `s`.///pubdefcountSubstring(substr: {substr = String}, s: String): Int32 =letsublen = length(substr#substr);defloop(offset, count) = matchindexOfLeftWithOffset(substr = substr#substr, offset = offset, s) {case None => countcase Some(i) => loop(i+sublen, count+1) };if (sublen<=0)0elseloop(0, 0)////// For every line in string `s`, strip a leading prefix consisting of/// blanks or control characters followed by `|` from the line, if/// such a prefix exists.///pubdefstripMargin(s: String): String = stripMarginWith({margin = "|"}, s)////// For every line in string `s`, strip a leading prefix consisting of/// blanks or control characters followed by `margin` from the line, if/// such a prefix exists.///pubdefstripMarginWith(margin: {margin = String}, s: String): String =letl = linesWithNewLine(s);intercalate("", List.map(stripMarginWithSingleLine(margin), l))////// Helper function for `stripMarginWith`./// From a single line, strips a leading prefix consisting of blanks or/// control characters followed by `margin`, if such a prefix exists.///defstripMarginWithSingleLine(margin: {margin = String}, s: String): String =letmarginLen = length(margin#margin);lettrimmed = dropWhile(c -> c<=' ', s);if (startsWith(prefix = margin#margin, trimmed))sliceRight(start = marginLen, trimmed)elses////// Helper function for `stripMarginWith`.////// Split the string `s` into an array of lines, breaking on newline,/// while keeping the newline at the end of each line.////// Newline is recognized as any Unicode linebreak sequence including/// Windows (carriage return, line feed) or Unix (line feed).///deflinesWithNewLine(s: String): List[String] =if (isEmpty(s)) Nilelsesplit({regex = "(?<=\\R)"}, s)////// Returns `Some(suffix)` of string `s` if its prefix matches `substr`.///pubdefstripPrefix(substr: {substr = String}, s: String): Option[String] =if (notstartsWith(prefix = substr#substr, s)) Noneelse Some(dropLeft(length(substr#substr), s))////// Returns `Some(prefix)` of string `s` if its suffix matches `substr`.///pubdefstripSuffix(substr: {substr = String}, s: String): Option[String] =if (notendsWith(suffix = substr#substr, s)) Noneelse Some(dropRight(length(substr#substr), s))////// Wrap the string `s` into lines that are each at most as long as `w`, breaking on whitespace.////// If a line contains a single word that exceeds the length of `w`, the word will be broken.///pubdefwrap(w: Int32, s: String): String =if (w<=0)""else {letlines = List.flatMap(l -> wrapLine(w, l), lines(s));intercalate(lineSeparator(), lines) }////// Helper for wrap./// Takes a single line, `s`, and returns a list consisting of the split lines, each being no longer than `w`.///defwrapLine(w: Int32, s: String): List[String] =if (length(s) <=w)s :: Nilelse {// Find the last occurrence of non-whitespace followed by whitespace// This is the end of the first lineletendOpt = Regex.indexOfLastWithBounds(regex"(?<!\\s)\\s", {start = 0}, {end = w}, s);letend = matchendOpt {case Some(end) => endcase None => w };sliceLeft({end = end}, s) :: wrapLine(w, trimLeft(sliceRight({start = end}, s))) }////// Unwrap the string, replacing every newline with a space///pubdefunwrap(s: String): String =Regex.replace({src = regex"[\r\n]+"}, {dst = " "}, s)////// Returns an iterator over `s`.///pubdefiterator(rc: Region[r], s: String): Iterator[Char, r, r] \ r =Iterator.range(rc, 0, length(s)) |> Iterator.map(i -> charAt(i, s))}