Functions for working with text (UTF-8 strings) and character values.
pub fn len(self: text) -> integer
Number of characters (Unicode code points) in the text — the human count, as in every mainstream language. For a byte length (bounds checks, the limit of byte-positioned indexing / slicing) use size.
pub fn size(self: text) -> integer
Number of bytes in the text. This is the bound for byte-positioned operations: s[i], slices s[a..b], and find/rfind all use byte offsets. For the human character count use len.
pub operator compare(self: text, other: text) -> Ordering
Where self stands against other, in the order < uses: by bytes, as < compares text.
pub operator compare(self: character, other: character) -> Ordering
Where self stands against other, by code point, in the order < uses.
pub fn split(self: text, separator: character) -> vector<text>
Splits self on every occurrence of separator and returns the parts as a vector. Use to parse CSV lines or space-separated tokens. Every separator ends a part, so a trailing separator leaves an empty last part ("a,".split(',') is ["a", ""]); an empty text has no parts at all.
pub fn split_text(self: text, separator: text) -> vector<text>
Split on a multi-character text separator. "a, b, c".split_text(", ") returns ["a", "b", "c"]. Edge cases: - empty self → empty result - empty separator → returns [self] unchanged (no infinite-split) - separator never matches → returns [self] - separator at start / end → produces empty boundary entries
pub fn starts_with_at(self: text, pos: integer, prefix: text) -> boolean
Functions for searching, transforming, and classifying text and character values. Character classification functions return true only if every character in the text satisfies the condition. The single-character variants test one code point. Returns true if self contains prefix starting at byte position pos. Sugar over self[pos..pos + prefix.size()] == prefix for the common "is this token at this offset?" pattern in scanners / parsers. Returns false (not an error) when pos + prefix.size() exceeds self.size() — same shape as starts_with for the "prefix too long for input" case.
Faster than comparing characters one at a time for a known prefix at pos.
pub fn char_slice(self: text, from: integer, to: integer) -> text
The characters of self from from up to (not including) to — a slice that counts CHARACTERS where self[a..b] counts BYTES.
⚠ **This is the cure for the commonest text bug in this stack.** len(text) counts characters while self[a..b] slices bytes, and loft snaps a byte cut outward to a character boundary — so a count computed from len and applied as a byte range fits FEWER characters than it measured. The failure is not mojibake but something quieter: the text comes back SHORT, silently, and only when it is not ASCII, so every ASCII test stays green. "héllo"[0..4] is "hél"; "héllo".char_slice(0, 4) is "héll".
Use it wherever a character COUNT becomes a cut: fitting a label to a width, wrapping a line, a caret or a selection in a text field, truncating with an ellipsis. Reach for self[a..b] only when the numbers came from a byte source — find, size, byte_at.
Negative bounds count from the end, as a byte slice's do (char_slice(-2, n) is the last two characters); both ends clamp, and a reversed or empty range answers "" rather than failing.
pub fn trim(self: text) -> text[self]
Removes leading and trailing whitespace. Use when processing user input or file content.
pub fn trim_start(self: text) -> text[self]
Removes leading whitespace only.
pub fn find(self: text, value: text) -> integer?
Returns the byte index of the first occurrence of value, or null if not found. Use to locate substrings before slicing. Nullable: null when value is absent (the type is honest about the not-found case).
pub fn rfind(self: text, value: text) -> integer?
Returns the byte index of the last occurrence of value, or null if not found. Use to find file extensions or the last path separator. Nullable: null when value is absent (the type is honest about not-found).