std.str
Reed's standard library. Imported with use std.str; not on disk.
Functions
func str_empty() -> &Str
An empty string.
A function rather than a global constant, because a global in the standard library would be shared mutable module state and would occupy a name in the user's flat namespace (see agents/stdlib-design.md).
- Returns
- a zero-length string
Source
func str_empty() -> &Str {
return new Str[0]{};
}
func str_repeat_byte(value: i32, count: i32) -> &Str
A string of count copies of byte value.
A negative count yields an empty string rather than trapping.
- Parameters
- value — the byte to repeat
- count — how many copies
- Returns
- the filled string
Source
func str_repeat_byte(value: i32, count: i32) -> &Str {
if (count <= 0) { return str_empty(); }
let out: &Str = new Str[count]{};
out.fill(0, value, count);
return out;
}
func str_from_byte(value: i32) -> &Str
A one-byte string holding value.
- Parameters
- value — the byte
- Returns
- a string of length 1
Source
func str_from_byte(value: i32) -> &Str {
let out: &Str = new Str[1]{};
out[0] = value;
return out;
}
func str_concat_all(parts: &StrList) -> &Str
Concatenates every string in parts into one.
Sizes the result exactly, in one pass over the lengths, so building a long string from many pieces costs one allocation rather than one per piece -- the quadratic-copy trap that repeated concat in a loop falls into.
Null elements are skipped.
- Parameters
- parts — the strings to join, in order
- Returns
- a new string holding every part
- See also
Str.concat, str_join
Source
func str_concat_all(parts: &StrList) -> &Str {
var total: i32 = 0;
for i in 0..#parts {
let part: ?Str = parts[i];
if (part is &Str) { total = total + #part; }
}
let out: &Str = new Str[total]{};
var at: i32 = 0;
for i in 0..#parts {
let part: ?Str = parts[i];
if (part is &Str) {
out.copy(at, part, 0, #part);
at = at + #part;
}
}
return out;
}
func str_join(parts: &StrList, sep: &Str) -> &Str
Joins parts with sep between consecutive elements.
The exact inverse of split: str_join(s.split(sep), sep) reproduces s.
Null elements are treated as empty strings, so the separator count still matches the element count.
- Parameters
- parts — the strings to join
- sep — the separator to place between them
- Returns
- the joined string
- See also
Str.split, str_concat_all
Source
func str_join(parts: &StrList, sep: &Str) -> &Str {
if (#parts == 0) { return str_empty(); }
var total: i32 = #sep * (#parts - 1);
for i in 0..#parts {
let part: ?Str = parts[i];
if (part is &Str) { total = total + #part; }
}
let out: &Str = new Str[total]{};
var at: i32 = 0;
for i in 0..#parts {
if (i > 0) {
out.copy(at, sep, 0, #sep);
at = at + #sep;
}
let part: ?Str = parts[i];
if (part is &Str) {
out.copy(at, part, 0, #part);
at = at + #part;
}
}
return out;
}
func str_from_int(value: i32, base: i32) -> &Str
Formats a signed integer in the given base.
A base outside 2..=36 yields an empty string. Digits above 9 are lowercase.
Correct at i32 minimum, where negating the value to format its magnitude would overflow: the digits are accumulated from the negative side for exactly that reason, which is the single subtlest thing in this module.
- Parameters
- base — the radix,
2..=36
- Returns
- the formatted string
- See also
Str.parse_int
Source
func str_from_int(value: i32, base: i32) -> &Str {
if (base < 2 || base > 36) { return str_empty(); }
if (value == 0) { return str_from_byte(48); }
let negative: i32 = value < 0;
var rest: i32 = negative != 0 ? value : 0 - value;
var digits: i32 = 0;
var probe: i32 = rest;
loop measure() {
digits = digits + 1;
probe = probe / base;
if (probe == 0) { break measure(); }
continue measure();
}
let width: i32 = digits + (negative != 0 ? 1 : 0);
let out: &Str = new Str[width]{};
if (negative != 0) { out[0] = 45; }
var at: i32 = width;
loop emit() {
at = at - 1;
out[at] = (0 - (rest % base)).digit_char();
rest = rest / base;
if (rest == 0) { break emit(); }
continue emit();
}
return out;
}
func str_encode_utf8(code: i32) -> &Str
Encodes one code point as a UTF-8 string of one to four bytes.
A value outside 0..=0x10FFFF, or a surrogate in 0xD800..=0xDFFF (which UTF-8 must not encode), yields the replacement character U+FFFD rather than invalid bytes.
- Parameters
- code — the Unicode code point
- Returns
- a string holding its UTF-8 encoding
- See also
Str.decode_utf8
Source
func str_encode_utf8(code: i32) -> &Str {
var c: i32 = code;
if (c < 0 || c > 0x10FFFF || (c >= 0xD800 && c <= 0xDFFF)) { c = 0xFFFD; }
if (c < 0x80) { return str_from_byte(c); }
if (c < 0x800) {
let out: &Str = new Str[2]{};
out[0] = 0xC0 | (c >> 6);
out[1] = 0x80 | (c & 0x3F);
return out;
}
if (c < 0x10000) {
let out: &Str = new Str[3]{};
out[0] = 0xE0 | (c >> 12);
out[1] = 0x80 | ((c >> 6) & 0x3F);
out[2] = 0x80 | (c & 0x3F);
return out;
}
let out: &Str = new Str[4]{};
out[0] = 0xF0 | (c >> 18);
out[1] = 0x80 | ((c >> 12) & 0x3F);
out[2] = 0x80 | ((c >> 6) & 0x3F);
out[3] = 0x80 | (c & 0x3F);
return out;
}
Methods
| Str.len | The number of bytes in self, which is its length in characters only for ASCII. |
| Str.is_empty | Whether self has no bytes. |
| Str.byte | The unsigned byte at index. |
| Str.try_byte | The unsigned byte at index, or -1 when index is out of bounds. |
| Str.clone | An independent copy of self. |
| Str.concat | Concatenates two strings into a new one. |
| Str.slice | The bytes from from_index up to but not including end. |
| Str.substr | count bytes starting at from_index, clamped like slice. |
| Str.equals | Whether self and rhs hold the same bytes. |
| Str.equals_ignore_case | Whether self and rhs hold the same bytes, ignoring ASCII case. |
| Str.compare | Lexicographic byte-wise comparison. |
| Str.index_of | The offset of the first occurrence of needle at or after from, or -1. |
| Str.last_index_of | The offset of the last occurrence of needle, or -1. |
| Str.index_of_byte | The offset of the first byte equal to value at or after from, or -1. |
| Str.contains | Whether needle occurs anywhere in self. |
| Str.starts_with | Whether self begins with prefix. |
| Str.ends_with | Whether self ends with suffix. |
| Str.count | How many times needle occurs in self, counting non-overlapping matches. |
| Str.to_lower | A copy of self with every ASCII letter lowercased. |
| Str.to_upper | A copy of self with every ASCII letter uppercased. |
| Str.trim | A copy of self with leading and trailing ASCII whitespace removed. |
| Str.trim_start | A copy of self with leading ASCII whitespace removed. |
| Str.trim_end | A copy of self with trailing ASCII whitespace removed. |
| Str.reverse | self reversed, byte by byte. |
| Str.repeat | self repeated count times. |
| Str.replace | A copy of self with every occurrence of from replaced by to. |
| Str.pad_start | self left-padded with value bytes to at least width bytes. |
| Str.pad_end | self right-padded with value bytes to at least width bytes. |
| Str.split | Splits self on every occurrence of sep. |
| Str.split_byte | Splits self on every occurrence of a single byte. |
| Str.split_whitespace | Splits self on runs of ASCII whitespace, discarding empty pieces. |
| StrList.at | Element index of a string list, as a non-null string. |
| StrList.len | How many strings the list holds. |
| StrList.is_empty | Whether the list holds no strings. |
| StrList.index_of | The index of the first element equal to needle, comparing by content, or -1. |
| StrList.contains | Whether any element equals needle, comparing by content. |
| StrList.non_empty | A new list holding only the non-empty elements. |
| Str.parse_int | Parses a signed integer in the given base. |
| Str.char_count | The number of UTF-8 code points in self. |
| Str.decode_utf8 | Decodes the UTF-8 code point beginning at index. |
| Str.is_valid_utf8 | Whether self is well-formed UTF-8. |
| Str.hash | A 32-bit FNV-1a hash of self's bytes. |
func Str.len(self: &Str) -> i32
The number of bytes in self, which is its length in characters only for ASCII.
- Returns
- the byte length
- See also
Str.char_count
Source
func Str.len(self: &Str) -> i32 {
return #self;
}
func Str.is_empty(self: &Str) -> i32
Whether self has no bytes.
- Returns
1 when empty, 0 otherwise
Source
func Str.is_empty(self: &Str) -> i32 {
return #self == 0;
}
func Str.byte(self: &Str, index: i32) -> i32
The unsigned byte at index.
Traps when index is out of bounds, which is WebAssembly's own array.get behaviour and is deliberately not softened to a sentinel: an out-of-range index is a bug in the caller, and a silent 0 would hide it inside whatever the caller does next.
- Parameters
- index — the byte offset
- Returns
- the byte value,
0..=255
- See also
Str.try_byte
Source
func Str.byte(self: &Str, index: i32) -> i32 {
return self[index].u;
}
func Str.try_byte(self: &Str, index: i32) -> i32
The unsigned byte at index, or -1 when index is out of bounds.
The non-trapping counterpart of byte, for a scanner that would otherwise need its own bounds test before every read.
- Parameters
- index — the byte offset
- Returns
- the byte value, or
-1 when out of range
- See also
Str.byte
Source
func Str.try_byte(self: &Str, index: i32) -> i32 {
if (index < 0 || index >= #self) { return -1; }
return self[index].u;
}
func Str.clone(self: &Str) -> &Str
An independent copy of self.
Every operation in this module already returns a fresh string, so this is needed only when a caller wants to mutate a string it received without disturbing the original.
- Returns
- a new string with the same bytes
Source
func Str.clone(self: &Str) -> &Str {
let out: &Str = new Str[#self]{};
out.copy(0, self, 0, #self);
return out;
}
func Str.concat(self: &Str, rhs: &Str) -> &Str
Concatenates two strings into a new one.
- Parameters
- rhs — the string to append
- Returns
- a new string holding
self then rhs
- See also
str_concat_all
Source
func Str.concat(self: &Str, rhs: &Str) -> &Str {
let out: &Str = new Str[#self + #rhs]{};
out.copy(0, self, 0, #self);
out.copy(#self, rhs, 0, #rhs);
return out;
}
func Str.slice(self: &Str, from_index: i32, end: i32) -> &Str
The bytes from from_index up to but not including end.
Both endpoints are clamped into 0..=len and an inverted range yields an empty string, so no combination of arguments traps. That is the opposite convention from byte above, deliberately: slicing is routinely driven by computed offsets that legitimately run off the end (a scan that found nothing), while an out-of-range single-byte read is a bug.
- Parameters
- from_index — the first byte offset, inclusive
- end — the offset one past the last byte
- Returns
- a new string holding the selected bytes
- See also
Str.substr
Source
func Str.slice(self: &Str, from_index: i32, end: i32) -> &Str {
let lo: i32 = from_index < 0 ? 0 : (from_index > #self ? #self : from_index);
let hi: i32 = end < 0 ? 0 : (end > #self ? #self : end);
if (hi <= lo) { return str_empty(); }
let out: &Str = new Str[hi - lo]{};
out.copy(0, self, lo, hi - lo);
return out;
}
func Str.substr(self: &Str, from_index: i32, count: i32) -> &Str
count bytes starting at from_index, clamped like slice.
- Parameters
- from_index — the first byte offset
- count — how many bytes
- Returns
- a new string holding the selected bytes
- See also
Str.slice
Source
func Str.substr(self: &Str, from_index: i32, count: i32) -> &Str {
if (count <= 0) { return str_empty(); }
return self.slice(from_index, from_index + count);
}
func Str.equals(self: &Str, rhs: &Str) -> i32
Whether self and rhs hold the same bytes.
Content equality, not reference equality: two separately constructed strings with the same bytes are equal.
- Parameters
- rhs — the string to compare against
- Returns
1 when equal, 0 otherwise
- See also
Str.compare, Str.equals_ignore_case
Source
func Str.equals(self: &Str, rhs: &Str) -> i32 {
if (#self != #rhs) { return 0; }
for i in 0..#self {
if (self[i].u != rhs[i].u) { return 0; }
}
return 1;
}
func Str.equals_ignore_case(self: &Str, rhs: &Str) -> i32
Whether self and rhs hold the same bytes, ignoring ASCII case.
Only ASCII case folds; see std.ascii's scope note.
- Parameters
- rhs — the string to compare against
- Returns
1 when equal ignoring case, 0 otherwise
- See also
Str.equals
Source
func Str.equals_ignore_case(self: &Str, rhs: &Str) -> i32 {
if (#self != #rhs) { return 0; }
for i in 0..#self {
if (self[i].u.to_lower() != rhs[i].u.to_lower()) { return 0; }
}
return 1;
}
func Str.compare(self: &Str, rhs: &Str) -> i32
Lexicographic byte-wise comparison.
Compares bytes as unsigned, so ordering matches how UTF-8 sorts: a byte above 127 orders after every ASCII byte rather than before it, which signed comparison would get backwards. When one string is a prefix of the other, the shorter orders first.
The magnitude of a nonzero result is unspecified and carries no meaning; only its sign does. This is the shape sort's comparators expect.
- Parameters
- rhs — the string to compare against
- Returns
- negative, zero, or positive as
self orders before, equal to, or after rhs
- See also
Str.equals
Source
func Str.compare(self: &Str, rhs: &Str) -> i32 {
let n: i32 = #self < #rhs ? #self : #rhs;
for i in 0..n {
let d: i32 = self[i].u - rhs[i].u;
if (d != 0) { return d; }
}
return #self - #rhs;
}
func Str.index_of(self: &Str, needle: &Str, from: i32) -> i32
The offset of the first occurrence of needle at or after from, or -1.
An empty needle matches at from (clamped into range), which is the convention that makes split on an empty separator terminate and matches every mainstream library.
Straightforward O(n*m) scanning rather than Boyer-Moore: the preprocessing a sublinear algorithm needs costs more than the scan itself for the short needles that dominate, and the constant-factor difference is not worth an algorithm whose bugs are subtle.
- Parameters
- needle — the string to search for
- from — where to start searching
- Returns
- the byte offset of the match, or
-1 when absent
- See also
Str.contains, Str.last_index_of
Source
func Str.index_of(self: &Str, needle: &Str, from: i32) -> i32 {
let scan_from: i32 = from < 0 ? 0 : from;
if (#needle == 0) { return scan_from > #self ? #self : scan_from; }
if (#needle > #self) { return -1; }
let last: i32 = #self - #needle;
var at: i32 = scan_from;
loop scan() {
if (at > last) { break scan(); }
var matched: i32 = 1;
for k in 0..#needle {
if (self[at + k].u != needle[k].u) { matched = 0; }
}
if (matched != 0) { return at; }
at = at + 1;
continue scan();
}
return -1;
}
func Str.last_index_of(self: &Str, needle: &Str) -> i32
The offset of the last occurrence of needle, or -1.
- Parameters
- needle — the string to search for
- Returns
- the byte offset of the last match, or
-1 when absent
- See also
Str.index_of
Source
func Str.last_index_of(self: &Str, needle: &Str) -> i32 {
if (#needle == 0) { return #self; }
if (#needle > #self) { return -1; }
var at: i32 = #self - #needle;
loop scan() {
if (at < 0) { break scan(); }
var matched: i32 = 1;
for k in 0..#needle {
if (self[at + k].u != needle[k].u) { matched = 0; }
}
if (matched != 0) { return at; }
at = at - 1;
continue scan();
}
return -1;
}
func Str.index_of_byte(self: &Str, value: i32, from: i32) -> i32
The offset of the first byte equal to value at or after from, or -1.
The single-byte counterpart of index_of, avoiding the allocation a one-byte needle would need.
- Parameters
- value — the byte to search for
- from — where to start searching
- Returns
- the byte offset, or
-1 when absent
Source
func Str.index_of_byte(self: &Str, value: i32, from: i32) -> i32 {
let scan_from: i32 = from < 0 ? 0 : from;
for i in scan_from..#self {
if (self[i].u == value) { return i; }
}
return -1;
}
func Str.contains(self: &Str, needle: &Str) -> i32
Whether needle occurs anywhere in self.
- Parameters
- needle — the string to search for
- Returns
1 when present, 0 otherwise
- See also
Str.index_of
Source
func Str.contains(self: &Str, needle: &Str) -> i32 {
return self.index_of(needle, 0) >= 0;
}
func Str.starts_with(self: &Str, prefix: &Str) -> i32
Whether self begins with prefix.
- Parameters
- prefix — the string to test for
- Returns
1 when self starts with prefix, 0 otherwise
- See also
Str.ends_with
Source
func Str.starts_with(self: &Str, prefix: &Str) -> i32 {
if (#prefix > #self) { return 0; }
for i in 0..#prefix {
if (self[i].u != prefix[i].u) { return 0; }
}
return 1;
}
func Str.ends_with(self: &Str, suffix: &Str) -> i32
Whether self ends with suffix.
- Parameters
- suffix — the string to test for
- Returns
1 when self ends with suffix, 0 otherwise
- See also
Str.starts_with
Source
func Str.ends_with(self: &Str, suffix: &Str) -> i32 {
if (#suffix > #self) { return 0; }
let offset: i32 = #self - #suffix;
for i in 0..#suffix {
if (self[offset + i].u != suffix[i].u) { return 0; }
}
return 1;
}
func Str.count(self: &Str, needle: &Str) -> i32
How many times needle occurs in self, counting non-overlapping matches.
An empty needle yields 0 rather than the infinity the literal reading implies.
- Parameters
- needle — the string to count
- Returns
- the number of non-overlapping occurrences
Source
func Str.count(self: &Str, needle: &Str) -> i32 {
if (#needle == 0) { return 0; }
var found: i32 = 0;
var at: i32 = 0;
loop scan() {
let hit: i32 = self.index_of(needle, at);
if (hit < 0) { break scan(); }
found = found + 1;
at = hit + #needle;
continue scan();
}
return found;
}
func Str.to_lower(self: &Str) -> &Str
A copy of self with every ASCII letter lowercased.
- Returns
- a new lowercased string
- See also
Str.to_upper
Source
func Str.to_lower(self: &Str) -> &Str {
let out: &Str = new Str[#self]{};
for i in 0..#self {
out[i] = self[i].u.to_lower();
}
return out;
}
func Str.to_upper(self: &Str) -> &Str
A copy of self with every ASCII letter uppercased.
- Returns
- a new uppercased string
- See also
Str.to_lower
Source
func Str.to_upper(self: &Str) -> &Str {
let out: &Str = new Str[#self]{};
for i in 0..#self {
out[i] = self[i].u.to_upper();
}
return out;
}
func Str.trim(self: &Str) -> &Str
A copy of self with leading and trailing ASCII whitespace removed.
- Returns
- the trimmed string
- See also
Str.trim_start, Str.trim_end
Source
func Str.trim(self: &Str) -> &Str {
var lo: i32 = 0;
var hi: i32 = #self;
loop front() {
if (lo < hi && self[lo].u.is_space() != 0) { lo = lo + 1; continue front(); }
}
loop back() {
if (hi > lo && self[hi - 1].u.is_space() != 0) { hi = hi - 1; continue back(); }
}
return self.slice(lo, hi);
}
func Str.trim_start(self: &Str) -> &Str
A copy of self with leading ASCII whitespace removed.
- Returns
- the trimmed string
- See also
Str.trim
Source
func Str.trim_start(self: &Str) -> &Str {
var lo: i32 = 0;
loop front() {
if (lo < #self && self[lo].u.is_space() != 0) { lo = lo + 1; continue front(); }
}
return self.slice(lo, #self);
}
func Str.trim_end(self: &Str) -> &Str
A copy of self with trailing ASCII whitespace removed.
- Returns
- the trimmed string
- See also
Str.trim
Source
func Str.trim_end(self: &Str) -> &Str {
var hi: i32 = #self;
loop back() {
if (hi > 0 && self[hi - 1].u.is_space() != 0) { hi = hi - 1; continue back(); }
}
return self.slice(0, hi);
}
func Str.reverse(self: &Str) -> &Str
self reversed, byte by byte.
Reverses bytes, so this is correct for ASCII and scrambles multi-byte UTF-8. There is deliberately no reverse_chars: it would need to decode, and a caller who has decoded already has the code points.
- Returns
- a new string with the bytes in the opposite order
Source
func Str.reverse(self: &Str) -> &Str {
let out: &Str = new Str[#self]{};
for i in 0..#self {
out[i] = self[#self - 1 - i].u;
}
return out;
}
func Str.repeat(self: &Str, count: i32) -> &Str
self repeated count times.
A count of zero or less yields an empty string.
Doubles the result as it goes rather than appending self count times, so building a large repetition is logarithmic in the number of copies rather than quadratic in the bytes moved.
- Parameters
- count — how many copies
- Returns
- a new string holding
count copies
Source
func Str.repeat(self: &Str, count: i32) -> &Str {
if (count <= 0 || #self == 0) { return str_empty(); }
let out: &Str = new Str[#self * count]{};
out.copy(0, self, 0, #self);
var filled: i32 = #self;
let total: i32 = #self * count;
loop grow() {
if (filled >= total) { break grow(); }
let chunk: i32 = filled * 2 > total ? total - filled : filled;
out.copy(filled, out, 0, chunk);
filled = filled + chunk;
continue grow();
}
return out;
}
func Str.replace(self: &Str, from: &Str, to: &Str) -> &Str
A copy of self with every occurrence of from replaced by to.
An empty from returns self unchanged rather than inserting to between every byte.
- Parameters
- from — the substring to replace
- to — the replacement
- Returns
- a new string with the replacements applied
Source
func Str.replace(self: &Str, from: &Str, to: &Str) -> &Str {
if (#from == 0) { return self.clone(); }
let hits: i32 = self.count(from);
if (hits == 0) { return self.clone(); }
let out: &Str = new Str[#self + hits * (#to - #from)]{};
var read: i32 = 0;
var write: i32 = 0;
loop copy() {
let hit: i32 = self.index_of(from, read);
if (hit < 0) { break copy(); }
out.copy(write, self, read, hit - read);
write = write + (hit - read);
out.copy(write, to, 0, #to);
write = write + #to;
read = hit + #from;
continue copy();
}
out.copy(write, self, read, #self - read);
return out;
}
func Str.pad_start(self: &Str, width: i32, value: i32) -> &Str
self left-padded with value bytes to at least width bytes.
Returns self unchanged when it is already at least width long; never truncates.
- Parameters
- width — the minimum result length
- value — the padding byte
- Returns
- the padded string
- See also
Str.pad_end
Source
func Str.pad_start(self: &Str, width: i32, value: i32) -> &Str {
if (#self >= width) { return self.clone(); }
let out: &Str = new Str[width]{};
out.fill(0, value, width - #self);
out.copy(width - #self, self, 0, #self);
return out;
}
func Str.pad_end(self: &Str, width: i32, value: i32) -> &Str
self right-padded with value bytes to at least width bytes.
- Parameters
- width — the minimum result length
- value — the padding byte
- Returns
- the padded string
- See also
Str.pad_start
Source
func Str.pad_end(self: &Str, width: i32, value: i32) -> &Str {
if (#self >= width) { return self.clone(); }
let out: &Str = new Str[width]{};
out.copy(0, self, 0, #self);
out.fill(#self, value, width - #self);
return out;
}
func Str.split(self: &Str, sep: &Str) -> &StrList
Splits self on every occurrence of sep.
A separator that does not occur yields a one-element list holding self. Adjacent separators yield empty strings, and a leading or trailing separator yields an empty first or last element, so n separators always produce exactly n + 1 pieces and join inverts split exactly.
An empty separator yields a one-element list holding self, rather than splitting into individual bytes.
- Parameters
- sep — the separator
- Returns
- the pieces, in order
- See also
str_join, Str.split_byte
Source
func Str.split(self: &Str, sep: &Str) -> &StrList {
if (#sep == 0) {
let single: &StrList = new StrList[1]{};
single[0] = self.clone();
return single;
}
let pieces: i32 = self.count(sep) + 1;
let out: &StrList = new StrList[pieces]{};
var read: i32 = 0;
var index: i32 = 0;
loop cut() {
let hit: i32 = self.index_of(sep, read);
if (hit < 0) { break cut(); }
out[index] = self.slice(read, hit);
index = index + 1;
read = hit + #sep;
continue cut();
}
out[index] = self.slice(read, #self);
return out;
}
func Str.split_byte(self: &Str, sep: i32) -> &StrList
Splits self on every occurrence of a single byte.
The single-byte counterpart of split, avoiding a one-byte needle allocation.
- Parameters
- sep — the separator byte
- Returns
- the pieces, in order
- See also
Str.split
Source
func Str.split_byte(self: &Str, sep: i32) -> &StrList {
var pieces: i32 = 1;
for i in 0..#self {
if (self[i].u == sep) { pieces = pieces + 1; }
}
let out: &StrList = new StrList[pieces]{};
var read: i32 = 0;
var index: i32 = 0;
for i in 0..#self {
if (self[i].u == sep) {
out[index] = self.slice(read, i);
index = index + 1;
read = i + 1;
}
}
out[index] = self.slice(read, #self);
return out;
}
func Str.split_whitespace(self: &Str) -> &StrList
Splits self on runs of ASCII whitespace, discarding empty pieces.
Unlike split, leading, trailing, and repeated whitespace produce no empty elements, which is what tokenizing a line of text wants. The result may therefore be empty.
- Returns
- the non-empty whitespace-separated pieces
- See also
Str.split
Source
func Str.split_whitespace(self: &Str) -> &StrList {
var pieces: i32 = 0;
var in_word: i32 = 0;
for i in 0..#self {
let is_ws: i32 = self[i].u.is_space();
if (is_ws == 0 && in_word == 0) { pieces = pieces + 1; }
in_word = is_ws == 0;
}
let out: &StrList = new StrList[pieces]{};
var index: i32 = 0;
var word_start: i32 = -1;
for i in 0..#self {
let is_ws: i32 = self[i].u.is_space();
if (is_ws == 0 && word_start < 0) { word_start = i; }
if (is_ws != 0 && word_start >= 0) {
out[index] = self.slice(word_start, i);
index = index + 1;
word_start = -1;
}
}
if (word_start >= 0) { out[index] = self.slice(word_start, #self); }
return out;
}
func StrList.at(self: &StrList, index: i32) -> &Str
Element index of a string list, as a non-null string.
A null element or an out-of-range index yields an empty string, so a caller iterating a split result never needs its own narrowing. This is the function that makes StrList's nullable element type (forced by default-initialization, see StrList) invisible in ordinary use.
- Parameters
- index — which element
- Returns
- the element, or an empty string
Source
func StrList.at(self: &StrList, index: i32) -> &Str {
if (index < 0 || index >= #self) { return str_empty(); }
let item: ?Str = self[index];
if (item is &Str) { return item; }
return str_empty();
}
func StrList.len(self: &StrList) -> i32
How many strings the list holds.
Identical to #list. It exists so a StrList reads the same way as every other collection in the library (IntList.len, IntMap.len, BitSet.len) rather than being the one that needs a different spelling.
- Returns
- the element count
Source
func StrList.len(self: &StrList) -> i32 {
return #self;
}
func StrList.is_empty(self: &StrList) -> i32
Whether the list holds no strings.
- Returns
1 when empty, 0 otherwise
Source
func StrList.is_empty(self: &StrList) -> i32 {
return #self == 0;
}
func StrList.index_of(self: &StrList, needle: &Str) -> i32
The index of the first element equal to needle, comparing by content, or -1.
- Parameters
- needle — the string to look for
- Returns
- the index, or
-1 when absent
- See also
StrList.contains
Source
func StrList.index_of(self: &StrList, needle: &Str) -> i32 {
for i in 0..#self {
if (self.at(i).equals(needle)) { return i; }
}
return -1;
}
func StrList.contains(self: &StrList, needle: &Str) -> i32
Whether any element equals needle, comparing by content.
- Parameters
- needle — the string to look for
- Returns
1 when present, 0 otherwise
Source
func StrList.contains(self: &StrList, needle: &Str) -> i32 {
return self.index_of(needle) >= 0;
}
func StrList.non_empty(self: &StrList) -> &StrList
A new list holding only the non-empty elements.
The companion to split, whose contract deliberately preserves empty pieces so that str_join inverts it exactly. When the empty pieces are noise rather than data -- parsing a,,b as two fields, say -- this drops them in one call.
- Returns
- a new list without the empty strings
- See also
Str.split, Str.split_whitespace
Source
func StrList.non_empty(self: &StrList) -> &StrList {
var kept: i32 = 0;
for i in 0..#self {
if (self.at(i).is_empty() == 0) { kept = kept + 1; }
}
let out: &StrList = new StrList[kept]{};
var at: i32 = 0;
for i in 0..#self {
let item: &Str = self.at(i);
if (item.is_empty() == 0) {
out[at] = item;
at = at + 1;
}
}
return out;
}
func Str.parse_int(self: &Str, base: i32) -> (i32, i32)
Parses a signed integer in the given base.
Accepts an optional leading +/- and then one or more digits valid in base (0-9, a-z, either case). Leading and trailing whitespace is not accepted; trim first if the input may carry any.
Returns ok = 0 for empty input, a bad digit, trailing junk, or a base outside 2..=36. That two-result shape is why this is not simply -1 on failure: -1 is a perfectly good parse result, and conflating the two is a classic source of bugs.
Overflow wraps rather than failing, matching * and +.
- Parameters
- base — the radix,
2..=36
- Returns
(value, ok) -- the parsed value, and 1 when the whole input parsed
- See also
str_from_int
Source
func Str.parse_int(self: &Str, base: i32) -> (i32, i32) {
if (base < 2 || base > 36 || #self == 0) { return (0, 0); }
var at: i32 = 0;
var negative: i32 = 0;
let first: i32 = self[0].u;
if (first == 45 || first == 43) {
negative = first == 45;
at = 1;
}
if (at >= #self) { return (0, 0); }
var value: i32 = 0;
loop digits() {
if (at >= #self) { break digits(); }
let d: i32 = self[at].u.digit_value();
if (d < 0 || d >= base) { return (0, 0); }
value = value * base + d;
at = at + 1;
continue digits();
}
return (negative != 0 ? 0 - value : value, 1);
}
func Str.char_count(self: &Str) -> i32
The number of UTF-8 code points in self.
Counts bytes that are not continuation bytes (0b10xxxxxx), so it is correct for well-formed UTF-8 and never traps on malformed input -- it simply counts what looks like a leading byte.
- Returns
- the code point count
- See also
Str.len, Str.decode_utf8
Source
func Str.char_count(self: &Str) -> i32 {
var count: i32 = 0;
for i in 0..#self {
if ((self[i].u & 0xC0) != 0x80) { count = count + 1; }
}
return count;
}
func Str.decode_utf8(self: &Str, index: i32) -> (i32, i32)
Decodes the UTF-8 code point beginning at index.
Returns the code point and the number of bytes it occupied, so a caller advances by adding the second result. Malformed or truncated input yields (0xFFFD, 1) -- the Unicode replacement character, consuming one byte -- so a decode loop over arbitrary bytes always terminates and never traps.
An out-of-range index yields (0xFFFD, 0); the zero width is what stops a loop rather than spinning forever at the end.
- Parameters
- index — the byte offset of the sequence's first byte
- Returns
(code_point, byte_width)
- See also
str_encode_utf8, Str.char_count
Source
func Str.decode_utf8(self: &Str, index: i32) -> (i32, i32) {
if (index < 0 || index >= #self) { return (0xFFFD, 0); }
let b0: i32 = self[index].u;
if (b0 < 0x80) { return (b0, 1); }
if (b0 < 0xC0 || b0 > 0xF4) { return (0xFFFD, 1); }
let width: i32 = b0 < 0xE0 ? 2 : (b0 < 0xF0 ? 3 : 4);
if (index + width > #self) { return (0xFFFD, 1); }
var value: i32 = b0 & (width == 2 ? 0x1F : (width == 3 ? 0x0F : 0x07));
for k in 1..width {
let cont: i32 = self[index + k].u;
if ((cont & 0xC0) != 0x80) { return (0xFFFD, 1); }
value = (value << 6) | (cont & 0x3F);
}
return (value, width);
}
func Str.is_valid_utf8(self: &Str) -> i32
Whether self is well-formed UTF-8.
Rejects overlong encodings, surrogates, values above U+10FFFF, truncated sequences, and stray continuation bytes -- the full set of rules, not just the length prefix, since accepting an overlong encoding is a real security hazard for anything that validates then re-decodes.
- Returns
1 when well-formed, 0 otherwise
- See also
Str.decode_utf8
Source
func Str.is_valid_utf8(self: &Str) -> i32 {
var at: i32 = 0;
loop scan() {
if (at >= #self) { break scan(); }
let b0: i32 = self[at].u;
if (b0 < 0x80) { at = at + 1; continue scan(); }
if (b0 < 0xC2 || b0 > 0xF4) { return 0; }
let width: i32 = b0 < 0xE0 ? 2 : (b0 < 0xF0 ? 3 : 4);
if (at + width > #self) { return 0; }
var value: i32 = b0 & (width == 2 ? 0x1F : (width == 3 ? 0x0F : 0x07));
for k in 1..width {
let cont: i32 = self[at + k].u;
if ((cont & 0xC0) != 0x80) { return 0; }
value = (value << 6) | (cont & 0x3F);
}
if (width == 3 && value < 0x800) { return 0; }
if (width == 4 && value < 0x10000) { return 0; }
if (value >= 0xD800 && value <= 0xDFFF) { return 0; }
if (value > 0x10FFFF) { return 0; }
at = at + width;
continue scan();
}
return 1;
}
func Str.hash(self: &Str) -> i32
A 32-bit FNV-1a hash of self's bytes.
Deterministic across runs and platforms, and equal for equal content. Suitable for a hash table, not for cryptography or for anything exposed to adversarial input: FNV has no collision resistance and is trivially invertible.
- Returns
- the hash value
- See also
std.map
Source
func Str.hash(self: &Str) -> i32 {
var h: i32 = 0x811C9DC5;
for i in 0..#self {
h = (h ^ self[i].u) * 0x01000193;
}
return h;
}
Types
| Str | A string: a garbage-collected array of bytes. |
| StrList | A list of strings, as returned by split and consumed by join. |
array Str { mut i32 }
A string: a garbage-collected array of bytes. See this module's header for why a string is a GC array rather than a region of linear memory.
array StrList { mut ?Str }
A list of strings, as returned by split and consumed by join.
Elements are nullable because a GC array must be default-initializable to be created with a runtime length (spec section 6.1); ?Str is defaultable and &Str is not. In practice every element a std.str function puts in one is non-null, and at narrows the nullability away for callers.