Functions for working with text (UTF-8 strings) and character values. Read the value of a variable and put a reference to it on the stack
pub fn len(both: text) -> integer
Number of characters (Unicode code points) in the text (@PLN110) — 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(both: text) -> integer
Number of bytes in the text (@PLN110). 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 fn len(both: character) -> integer
Byte length of the character's UTF-8 encoding (1–4).
Splits self on every occurrence of separator and returns the parts as a vector. Use to parse CSV lines or space-separated tokens.
pub fn split(self: text, separator: character) -> vector<text>
pub fn split_text(self: text, separator: text) -> vector<text>
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. (`starts_with` / `ends_with` moved to `02_files.loft` so the path helpers there can call them; both still available as `text.starts_with` / `text.ends_with`.) 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`. Example: @STD-001
pub fn trim(both: text) -> text[both]
(Path helpers `dir` / `basename` / `join` / `resolve` moved to `02_files.loft` § Path helpers, so they're available before `03_text.loft` loads — same call shape, same `pub fn` signatures.) 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 trim_end(self: text) -> text[self]
Removes trailing 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 (@PLN102 keystone step 4 — 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 (@PLN102 keystone step 4 — the type is honest about not-found).
pub fn contains(self: text, value: text) -> boolean
Returns true if value appears anywhere in self. `s.contains(v)` == `s.find(v) != null` — `find` returns the position, `contains` is its found?/not-found? projection. NOT superseded: `contains` is the clearer idiom for a membership test (a boolean predicate), and folding it onto `find` (below) is an implementation detail, not a reason to steer callers away from it.
pub fn replace(self: text, value: text, with: text) -> text
Returns a copy of self with every occurrence of value replaced by with.
pub fn to_lowercase(self: text) -> text
Returns a lowercase copy. Use for case-insensitive comparisons.
pub fn to_uppercase(self: text) -> text
Returns an uppercase copy.
pub fn is_lowercase(self: text) -> boolean
True if the text is non-empty and all characters are lowercase letters.
pub fn is_lowercase(self: character) -> boolean
True if the character is a lowercase letter.
pub fn is_uppercase(self: text) -> boolean
True if the text is non-empty and all characters are uppercase letters.
pub fn is_uppercase(self: character) -> boolean
True if the character is an uppercase letter.
pub fn is_numeric(self: text) -> boolean
True if the text is non-empty and all characters are numeric digits (Unicode numeric, not just ASCII 0–9).
pub fn is_numeric(self: character) -> boolean
True if the character is a numeric digit.
pub fn is_alphanumeric(self: text) -> boolean
True if the text is non-empty and all characters are letters or digits. Use to validate identifiers or tokens.
pub fn is_alphanumeric(self: character) -> boolean
True if the character is a letter or digit.
pub fn is_alphabetic(self: text) -> boolean
True if the text is non-empty and all characters are alphabetic.
pub fn is_alphabetic(self: character) -> boolean
True if the character is alphabetic.
pub fn is_whitespace(self: text) -> boolean
True if the text is non-empty and all characters are whitespace. Use to detect blank lines.
pub fn is_whitespace(self: character) -> boolean
True if the character is whitespace.
pub fn is_control(self: text) -> boolean
True if the text is non-empty and all characters are control characters.
pub fn is_control(self: character) -> boolean
True if the character is a control character.
pub fn join(self: vector<text>, sep: text) -> text
Joins parts with sep between each consecutive pair. Returns "" for an empty vector. Use to build comma-separated lists, path segments, or any delimited output. Example: @STD-003
pub fn byte_at(self: text, i: integer) -> integer
These two are TEXT functions that happen to sit after the Environment marker. Re-opening the section files them under Text in the generated reference, which is where a reader looks for them — `text_from_bytes` existed for two releases and was reported as missing (loft#748) because `doc/stdlib-text.html` did not list it. Declaring the section rather than MOVING the definitions is deliberate: definition order is the order types are minted, and the native `init()` replays that order, so relocating `vector<u8>`'s first mention shifts every type id after it (loft#739 / #742). A comment cannot. Return the BYTE at position `i` (0..len) as integer 0-255, or 0 for out-of-bounds. Unlike `text[i]` which decodes the UTF-8 codepoint containing byte `i` (walking back through continuation bytes), `byte_at(i)` is a pure O(1) byte read. Use in ASCII-heavy scanning hot paths (tokenisers, regex- like loops) where the UTF-8 decode is wasted work — every non-ASCII byte still returns a valid 0-255 number; the caller compares against ASCII constants so byte semantics suffice. ~5-10× faster than `text[i]` for pure-ASCII checks.
pub fn text_from_bytes(bytes: vector<u8>) -> text
Build a text from the raw UTF-8 bytes of a vector<u8> — the inverse of byte_at. Use in binary decoders (CBOR text, HPKE byte composition) that assemble a byte buffer and need to turn it back into text. Bytes that are not valid UTF-8 yield the empty text (never a crash); validate first if you must tell "empty input" from "invalid bytes" apart.
pub fn chr(cp: integer) -> text
Build a one-character text from a Unicode CODE POINT — the inverse of the `ch as integer` that text iteration already gives you, and the code-point twin of text_from_bytes' byte route. Use when you have a number and need the character it names: decoding an escape (`\u{...}`, an HTML entity), walking a code-point table, or reassembling text a code point at a time. chr(65) "A" chr(233) "é" chr(20013) "中" chr(128512) "😀" A code point that names no character answers the EMPTY text, never a crash: a surrogate (D800-DFFF), anything past U+10FFFF, a negative number — and also 0, because `character` uses 0 as its null and text iteration stops there, so a NUL built here could not be read back. If you need an embedded NUL, go through the byte route: `text_from_bytes([0])` carries one. Example: @STD-002