prelude.String
Part of prelude.
let String.reverse (s: String): String
--- Strings ---
All of these are built on chars_of, which is the one primitive in runtime.plum. That is what makes them codepoint-safe rather than byte-indexed, so String.slice can never split a multi-byte character in half, because it never sees bytes at all. The string with its CHARACTERS reversed, not its bytes — so text outside ASCII survives.
let String.is_empty (s: String): Bool
Whether the string has no bytes.
let String.slice (s: String) (start: Int) (end: Int): String
Characters from start up to but not including end, counted in CODEPOINTS rather than bytes. Out-of-range bounds are clamped.
let String.repeat (s: String) (n: Int): String
The string joined to itself n times. n of zero or less gives an empty string.
let String.char_len (s: String): Int
The number of CHARACTERS in s, which is not s.len().
.len() is bytes; everything else in this section is codepoints. The two agree on ASCII and part company on anything else, so a program that lines text up in columns wants this one; padding computed from a byte count puts an accented name in the wrong place. The number of CHARACTERS (codepoints). String.len is the number of BYTES, and the two differ for anything outside ASCII.
let String.pad_left (s: String) (width: Int) (fill: String): String
Pad s to width CHARACTERS with fill, on the left or the right.
A fill that is not exactly one character returns s untouched, as does a string already at least width wide. Both are deliberate: a formatting helper that returns a Result is a formatting helper nobody uses, and truncating to fit loses data that padding was never asked to remove. Padded on the LEFT to width characters — right-aligned text.
fill must be a single character. A string already at or past the width is returned unchanged: this pads, it never truncates.
let String.pad_right (s: String) (width: Int) (fill: String): String
Padded on the RIGHT to width characters — left-aligned text. Never truncates; see String.pad_left.
let String.pad_center (s: String) (width: Int) (fill: String): String
Centred padding, completing the set beside pad_left/pad_right. An odd remainder goes on the RIGHT, so a column of centred headings lines up on its left edge rather than drifting.
let String.hash (s: String): Int
A hash of the string's bytes, for bucketing.
Not stable across releases and not cryptographic — do not persist it, and do not use it for anything security-related.
let String.is_ascii_ws (c: String): Bool
Whether a one-character string is an ASCII space, tab, newline or carriage return. Unicode spaces are not included.
let String.index_of (s: String) (needle: String): Option[Int]
The CHARACTER index where needle first appears, or None.
let String.len (s: String): Int
--- Namespaced forms of the string METHODS ---
.len(), .split(), .replace() and friends are compiler PRIMITIVES lowered straight to IR, so they existed only as methods, while String.slice/index_of/lines/repeat/ trim_start/trim_end are ordinary prelude functions and so existed only namespaced. That split tracks how each one happens to be implemented, which is invisible from outside and produced a genuinely confusing surface: "x".trim() worked and String.trim("x") did not, while String.trim_start("x") worked and "x".trim_start() did not. Same family, opposite rules, no way to tell which without trying.
The rule now: String.f(s, ..) always works. Some also have method sugar. These wrappers cost nothing when unused -- dead-function elimination drops them. The number of BYTES. String.char_len is the character count, and the two differ for anything outside ASCII.
let String.split (s: String) (sep: String): Array[String]
Splits on every occurrence of sep.
A separator at either end produces an empty piece, so splitting \",a,\" on \",\" gives three pieces. Splitting on an empty separator gives the characters.
let String.contains (s: String) (needle: String): Bool
Whether needle appears anywhere.
let String.starts_with (s: String) (prefix: String): Bool
Whether the string begins with prefix.
let String.ends_with (s: String) (suffix: String): Bool
Whether the string ends with suffix.
let String.replace (s: String) (from: String) (to: String): String
Every occurrence of from replaced with to. An empty from leaves the string alone rather than looping.
let String.to_upper (s: String): String
Uppercased. ASCII only: letters outside it are left as they are, so this is not a general Unicode case mapping.
let String.to_lower (s: String): String
Lowercased. ASCII only — see String.to_upper.
let String.trim (s: String): String
Whitespace removed from both ends, including Unicode spaces.
let String.as_bytes (s: String): Bytes
Free in both directions at runtime; the DIRECTION is what differs. Every String is bytes, so this one is total. The string's bytes, as UTF-8. No copy of the text is decoded, so this is the cheap direction.
let String.parse_int (s: String): Result[Int, String]
Parses a decimal integer, with an optional leading -.
Err on anything else, including an empty string, surrounding spaces, and a number too large for an Int. No 0x or 0b prefixes — this is the inverse of to_string, not a literal parser.
let String.parse_float (s: String): Result[Float, String]
String.parse_float DELEGATES to the JSON number parser below, exactly as the real prelude does.
It used to have a hand-written parser here, on the reasoning that dragging a JSON parser in to read a float would be absurd. That reasoning cost twice. The hand-written version accumulated the fraction digit by digit against a repeatedly-divided 0.1, so 0.000001 parsed as 1.0000000000000002e-06, invisible until floats started printing every digit that matters. And its own comment admitted the second cost: "no exponent, which is a real narrowing, stated rather than discovered". Stated, but it made the two compilers DISAGREE. String.parse_float("1e3") gave 1000 under the real compiler and an error here.
The JSON parser was already in this same prelude, already handling exponents, already correct. Reusing it is not absurd; writing a second one was.
let String.trim_start (s: String): String
Whitespace removed from the start.
let String.trim_end (s: String): String
Whitespace removed from the end.
let String.lines (s: String): Array[String]
Split on newlines.
A trailing newline produces a final empty piece, since the text after it is empty — which is what makes joining the result back with newlines give the original.