Skip to main content

STRINGS

Constant STRINGS 

Source
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.