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