//! Methods on `String`: length, slicing, searching, splitting, joining,
//! trimming and case.
//!
//! Every index and length here counts characters, the Unicode scalar values a
//! `Char` holds, never bytes; `bytes_from_string` gives a string's UTF-8
//! bytes. Most of these methods walk the whole string, so `len` and `char_at`
//! take time in proportion to its length: to visit every character, call
//! `chars` once and index the array it returns. Nothing here knows about
//! grapheme clusters, so a combining accent counts as a character of its own.
//!
//! Trimming, splitting, joining and searching strings, where every index
//! and length counts characters:
//!
//! ```
//! let line = " name = Nova "
//! let parts = line.trim().split(" = ")
//! assert_eq(parts.len(), 2)
//! assert_eq(parts[0], "name")
//! assert_eq(parts[1].to_upper(), "NOVA")
//! assert_eq(", ".join(["a", "b", "c"]), "a, b, c")
//!
//! // Indexes and lengths count characters, not bytes.
//! let word = "héllo"
//! assert_eq(word.len(), 5)
//! assert_eq(bytes_from_string(word).len(), 6)
//! assert_eq(word.slice(1, 3), "él")
//! assert_eq(word.index_of("llo").unwrap(), 2)
//! assert(word.starts_with("hé"), "starts_with compares characters")
//! ```
// Nova standard library — strings.
//
// Compiled as an implicit module and glob-imported into every user module, so
// these names need no `import` (see docs/adr/0004-stdlib-compile-model.md).
//
// EVERY index and length here is in CODEPOINTS (Unicode scalar values), never
// bytes. `NovaStr` stores UTF-8 and its own `len` field is a byte count, but
// no part of this module's surface exposes that.
//
// PERFORMANCE CONTRACT, worth reading before using this module in a loop:
// most operations that inspect the string decode the whole thing into a
// `[Char]` first, so `char_at` is O(n), and calling it for each index is
// QUADRATIC. Call `chars()` once and index the resulting array instead.
// `index_of`, `contains` and `join` do not decode: runtime builtins work on
// their bytes. This
// is the same class of rule as the `hash & (cap - 1)` note beside
// `pub trait Hash` in std/core — an invariant a caller has to know.
//
// This is the first inherent `impl String` in the language. Keep it as ONE
// block: whether two inherent impls on the same primitive in different std
// modules is rejected (`E0074` is specified for *trait* impls) or silently
// accepted with one shadowing the other is untested. An inherent method also
// wins by priority over a same-named trait method, so no method here may be
// named `fmt`, `dbg`, `eq`, `ne`, `cmp`, `clone`, `default` or `hash` — those
// are std/core's trait methods on `String` and would be silently shadowed.
// The codepoints `start..end` of `cs` as a string. `end` is exclusive.
// Callers have already validated the range. Neither half of the `n <= 0`
// guard is strictly needed for correctness: at `n == 0` the general path
// below already produces "" on its own (`[' '; 0]` is a legal repeat
// literal, the `0..0` loop is vacuous, and `str_from_chars` of an empty
// array is already ""), so that half is only a fast path skipping the
// allocation. The `n < 0` half is defensive rather than load-bearing today —
// `slice`, `split` and the `trim` family (`trim`, `trim_start`, `trim_end`)
// are this helper's only callers, and all of them already guarantee
// `start <= end` themselves — kept here in case a future caller of this
// module-private helper does not validate as carefully.
fn chars_to_string(cs: [Char], start: Int, end: Int) -> String {
let n = end - start
if n <= 0 { return "" }
// `[' '; n]` needs a filler; every slot is overwritten immediately below,
// so the space never survives. A repeat literal is the only way to
// allocate a runtime-length array.
let mut out = [' '; n]
for i in 0..n { out[i] = cs[start + i] }
str_from_chars(out)
}
// Whether `n` occurs in `h` starting exactly at `at`. A needle that would run
// past the end of `h` does not match; an empty needle matches at any `at`
// within `h`, including `h.len()`. Private: `starts_with`, `ends_with` and
// `split` are its only callers.
fn chars_match_at(h: [Char], n: [Char], at: Int) -> Bool {
// Every call site already guarantees `at >= 0` — `starts_with` passes the
// literal `0`; `ends_with` passes `h.len() - n.len()` only after checking
// `n.len() <= h.len()`; `split` only ever steps `i`/`j` upward starting
// from `0` — so this guard is currently unreachable. It
// is defensive rather than load-bearing today, kept in case a future
// caller of this module-private helper does not validate as carefully,
// matching `chars_to_string`'s analogous `n < 0` guard above.
if at < 0 { return false }
if at + n.len() > h.len() { return false }
let mut k = 0
while k < n.len() {
if h[at + k] != n[k] { return false }
k = k + 1
}
true
}
// Whether `c` is whitespace, for the `trim` family.
//
// DELIBERATE APPROXIMATION: this is an explicit list, not Unicode's full
// White_Space property. `Char` cannot currently be asked whether it is
// whitespace, and an exact answer would need a sixth permanent runtime ABI
// symbol, which is not worth it for this increment. It lives in exactly one
// place so there is one line to correct when that changes.
fn char_is_whitespace(c: Char) -> Bool {
if c == ' ' { return true }
if c == '\t' { return true }
if c == '\n' { return true }
if c == '\r' { return true }
// The non-ASCII spaces are compared by SCALAR VALUE, not as character
// literals: `'\u{00A0}'` DOES NOT LEX — the `\u{…}` escape works in a
// string literal but not in a char literal (verified; it fails with
// `L0001: unexpected character '`). `char_to_int` is already a std-only
// builtin, so an std module can compare codepoints directly.
let v = char_to_int(c)
if v == 160 { return true } // U+00A0 NO-BREAK SPACE
if v == 8194 { return true } // U+2002 EN SPACE
if v == 8195 { return true } // U+2003 EM SPACE
if v == 12288 { return true } // U+3000 IDEOGRAPHIC SPACE
false
}
// First index at or after 0 that is not whitespace; `cs.len()` if all of it is.
fn trim_start_index(cs: [Char]) -> Int {
let mut a = 0
while a < cs.len() { if char_is_whitespace(cs[a]) { a = a + 1 } else { return a } }
cs.len()
}
// Exclusive end index of the last non-whitespace codepoint at or after
// `floor`; `floor` if everything from there on is whitespace.
fn trim_end_index(cs: [Char], floor: Int) -> Int {
let mut b = cs.len()
while b > floor { if char_is_whitespace(cs[b - 1]) { b = b - 1 } else { return b } }
floor
}
impl String {
// Not `self.len() == 0`: `len()` decodes the whole string, while `==` on
// `String` compares byte lengths first in the runtime.
/// Returns `true` if the string is empty, `""`.
pub fn is_empty(self) -> Bool { self == "" }
// Codepoints, not bytes. O(n): the runtime walks the UTF-8.
/// Returns the number of characters in the string, not its bytes.
///
/// It walks the whole string to count them.
pub fn len(self) -> Int { str_len_chars(self) }
// The string's codepoints. Prefer this over repeated `char_at` — see the
// performance contract in the module header.
/// Returns the string's characters, in order, as an array.
pub fn chars(self) -> [Char] { str_chars(self) }
// The codepoint at `i`, or `None` when `i` is outside `0..len()`. A
// negative index is `None` rather than a panic, matching `Vec::get`.
/// Returns the character at index `i`, or `None` if `i` is negative or not
/// less than `len()`.
///
/// It decodes the whole string to find the character, so a loop calling
/// it for every index takes time in proportion to the square of the
/// length. To visit every character, call `chars` once and index its
/// array.
pub fn char_at(self, i: Int) -> Option<Char> {
if i < 0 { return None }
let cs = str_chars(self)
if i >= cs.len() { None } else { Some(cs[i]) }
}
// Codepoints `start..end`, with `end` EXCLUSIVE (half-open, like every
// other index range in the language). `start == end` yields "".
// Panics rather than returning an Option, matching `Vec::set` — an index
// that must be valid is a caller bug, whereas `char_at`'s query is not.
/// Returns the characters from index `start` up to, but not including,
/// `end`.
///
/// `start == end` gives `""`. Panics if `start` is negative, if `end` is
/// greater than `len()`, or if `start` is greater than `end`.
pub fn slice(self, start: Int, end: Int) -> String {
let cs = str_chars(self)
if start < 0 { panic("String::slice start is negative") }
if end > cs.len() { panic("String::slice end is past the end of the string") }
if start > end { panic("String::slice start is after end") }
chars_to_string(cs, start, end)
}
// Reverses CODEPOINTS, so a combining accent detaches from the character
// it modifies. That is inherent to codepoint-level operations and is why
// this module is not grapheme-aware (see the design doc, decision D1).
//
// The `n == 0` guard is not required for correctness — the general path
// below already produces "" for empty input on its own (`[' '; 0]` is a
// legal repeat literal, the `0..n` loop is then vacuous, and
// `str_from_chars` of an empty array is already "") — it exists purely
// as a fast path that skips the allocation for the empty string.
/// Returns the string with its characters in reverse order.
///
/// It reverses characters, not grapheme clusters, so a combining accent
/// comes apart from the character it modifies.
pub fn reverse(self) -> String {
let cs = str_chars(self)
let n = cs.len()
if n == 0 { return "" }
let mut out = [' '; n]
for i in 0..n { out[i] = cs[n - 1 - i] }
str_from_chars(out)
}
// Whether `self` begins with `prefix`, codepoint-for-codepoint. An empty
// `prefix` always matches: `chars_match_at` accepts `at == 0` trivially
// when `n` is empty.
/// Returns `true` if the string begins with `prefix`. An empty `prefix`
/// always matches.
pub fn starts_with(self, prefix: String) -> Bool {
chars_match_at(str_chars(self), str_chars(prefix), 0)
}
// Whether `self` ends with `suffix`, codepoint-for-codepoint. Guards
// `n.len() > h.len()` before subtracting so `h.len() - n.len()` never
// goes negative; an empty `suffix` still matches, since the subtraction
// then lands on `h.len()` itself, which `chars_match_at` accepts.
/// Returns `true` if the string ends with `suffix`. An empty `suffix`
/// always matches.
pub fn ends_with(self, suffix: String) -> Bool {
let h = str_chars(self)
let n = str_chars(suffix)
if n.len() > h.len() { return false }
chars_match_at(h, n, h.len() - n.len())
}
// Codepoint index of the FIRST occurrence, or `None`. An empty needle
// occurs at every position, so it reports `Some(0)`. A runtime builtin
// searches the bytes; it used to build a `[Char]` for each string first.
/// Returns the character index at which `needle` first occurs, or `None`
/// if it does not occur.
///
/// An empty `needle` occurs at index 0.
pub fn index_of(self, needle: String) -> Option<Int> {
let at = str_index_of(self, needle)
if at < 0 { return None }
Some(at)
}
// Whether `needle` occurs anywhere in `self`. An empty `needle` always
// matches, since it occurs at `0`. Calls the builtin directly, so a
// search allocates no `Option`.
/// Returns `true` if `needle` occurs in the string. An empty `needle`
/// always does.
pub fn contains(self, needle: String) -> Bool { str_index_of(self, needle) >= 0 }
// Split on every non-overlapping occurrence of `sep`, left to right.
//
// No separator found yields a ONE-element array holding the whole string,
// never an empty array. Adjacent, leading and trailing separators each
// produce an empty piece — nothing is collapsed or trimmed. An EMPTY
// separator splits into single codepoints (the JavaScript behaviour;
// Rust adds boundary empties and Python raises, so there is no consensus
// to inherit), and `"".split("")` is `[]`.
/// Splits the string at each occurrence of `sep`, and returns the pieces
/// in order.
///
/// Occurrences are found from left to right and do not overlap. Nothing
/// is collapsed or trimmed: two adjacent separators, or one at either end,
/// give an empty piece. A string without `sep` gives one piece, the whole
/// string, so `"".split(",")` is `[""]`. An empty `sep` splits the string
/// into its characters, and `"".split("")` is `[]`.
///
/// Nothing is collapsed or trimmed:
///
/// ```
/// // Nothing is collapsed or trimmed: adjacent separators give an empty piece.
/// let pieces = "a,,b,".split(",")
/// assert_eq(pieces.len(), 4)
/// assert_eq(pieces[1], "")
/// assert_eq(pieces[3], "")
/// assert_eq(",".join(pieces), "a,,b,") // `join` undoes `split`
///
/// assert_eq("".split(",").len(), 1) // one piece: the empty string
/// assert_eq("abc".split("").len(), 3) // an empty separator gives the characters
/// assert_eq("".split("").len(), 0)
/// ```
pub fn split(self, sep: String) -> [String] {
let h = str_chars(self)
let s = str_chars(sep)
if s.len() == 0 {
if h.len() == 0 { return [] }
let mut single = [""; h.len()]
for i in 0..h.len() { single[i] = chars_to_string(h, i, i + 1) }
return single
}
// Pass 1: count the pieces. One more piece than separators found.
let mut pieces = 1
let mut i = 0
while i + s.len() <= h.len() {
if chars_match_at(h, s, i) {
pieces = pieces + 1
i = i + s.len()
} else {
i = i + 1
}
}
// Pass 2: fill, walking the same way so the two passes agree.
let mut out = [""; pieces]
let mut w = 0
let mut start = 0
let mut j = 0
while j + s.len() <= h.len() {
if chars_match_at(h, s, j) {
out[w] = chars_to_string(h, start, j)
w = w + 1
j = j + s.len()
start = j
} else {
j = j + 1
}
}
out[w] = chars_to_string(h, start, h.len())
out
}
// Join `parts` with `self` between them: `",".join(parts)`. The separator
// is the receiver rather than this being a free `join(parts, sep)`,
// because a top-level `pub fn` is glob-imported into every module and
// would take the name `join` from all user code.
//
// A runtime builtin copies the parts' bytes straight into one result. It
// used to walk every part character by character into a `[Char]`.
/// Returns the strings of `parts` joined into one, with this string
/// between each pair: `",".join(parts)`.
///
/// An empty `parts` gives `""`.
pub fn join(self, parts: [String]) -> String { str_join(self, parts) }
// Trims whitespace (see `char_is_whitespace`) from both ends. An
// all-whitespace string, including "", yields "".
/// Returns the string without the whitespace at its start and end.
///
/// A string of whitespace alone gives `""`. Whitespace here is, for now,
/// a space, a tab, `\n`, `\r`, and the no-break, en, em and ideographic
/// spaces (U+00A0, U+2002, U+2003 and U+3000), not the rest of Unicode's
/// whitespace: a form feed, say, stays.
pub fn trim(self) -> String {
let cs = str_chars(self)
let a = trim_start_index(cs)
chars_to_string(cs, a, trim_end_index(cs, a))
}
// Trims whitespace from the start only.
/// Returns the string without the whitespace at its start, as `trim`
/// defines whitespace.
pub fn trim_start(self) -> String {
let cs = str_chars(self)
chars_to_string(cs, trim_start_index(cs), cs.len())
}
// Trims whitespace from the end only.
/// Returns the string without the whitespace at its end, as `trim`
/// defines whitespace.
pub fn trim_end(self) -> String {
let cs = str_chars(self)
chars_to_string(cs, 0, trim_end_index(cs, 0))
}
// `n` copies of `self`. Builds one array rather than concatenating, both
// because `String` has no `+` (E0013) and because repeated interpolation
// would be quadratic.
/// Returns `n` copies of the string, joined end to end.
///
/// `n == 0` gives `""`. Panics if `n` is negative.
pub fn repeat(self, n: Int) -> String {
if n < 0 { panic("String::repeat count must not be negative") }
let cs = str_chars(self)
let total = cs.len() * n
if total == 0 { return "" }
let mut out = [' '; total]
let mut w = 0
for r in 0..n {
for i in 0..cs.len() {
out[w] = cs[i]
w = w + 1
}
}
str_from_chars(out)
}
// Full Unicode case mapping, so the result may be LONGER than the input
// in codepoints: `"ß".to_upper()` is `"SS"`.
/// Returns the string in upper case, by Unicode's full case mapping.
///
/// The result can have more characters than the string: `"ß".to_upper()`
/// is `"SS"`.
pub fn to_upper(self) -> String { str_to_upper(self) }
// Full Unicode case mapping, for the same reason as `to_upper`: the
// result may differ in codepoint count from the input, just in the other
// direction — `"İ".to_lower()` is `"i"` followed by a combining dot
// above (U+0307), 2 codepoints from 1.
/// Returns the string in lower case, by Unicode's full case mapping.
///
/// The result can have more characters than the string: `"İ".to_lower()`
/// is `i` followed by a combining dot above, U+0307.
pub fn to_lower(self) -> String { str_to_lower(self) }
}