std.str

Reed's standard library. Imported with use std.str; not on disk.

Functions

str_emptyAn empty string.
str_repeat_byteA string of count copies of byte value.
str_from_byteA one-byte string holding value.
str_concat_allConcatenates every string in parts into one.
str_joinJoins parts with sep between consecutive elements.
str_from_intFormats a signed integer in the given base.
str_encode_utf8Encodes one code point as a UTF-8 string of one to four bytes.

str_empty #

line 98
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]{};
}

str_repeat_byte #

line 109
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;
}

str_from_byte #

line 120
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;
}

str_concat_all #

line 161
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;
}

str_join #

line 655
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;
}

str_from_int #

line 805
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;
  // Accumulate on the negative side so `i32` minimum works: its magnitude has no positive
  // representation, but every intermediate here stays <= 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;
}

str_encode_utf8 #

line 888
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.lenThe number of bytes in self, which is its length in characters only for ASCII.
Str.is_emptyWhether self has no bytes.
Str.byteThe unsigned byte at index.
Str.try_byteThe unsigned byte at index, or -1 when index is out of bounds.
Str.cloneAn independent copy of self.
Str.concatConcatenates two strings into a new one.
Str.sliceThe bytes from from_index up to but not including end.
Str.substrcount bytes starting at from_index, clamped like slice.
Str.equalsWhether self and rhs hold the same bytes.
Str.equals_ignore_caseWhether self and rhs hold the same bytes, ignoring ASCII case.
Str.compareLexicographic byte-wise comparison.
Str.index_ofThe offset of the first occurrence of needle at or after from, or -1.
Str.last_index_ofThe offset of the last occurrence of needle, or -1.
Str.index_of_byteThe offset of the first byte equal to value at or after from, or -1.
Str.containsWhether needle occurs anywhere in self.
Str.starts_withWhether self begins with prefix.
Str.ends_withWhether self ends with suffix.
Str.countHow many times needle occurs in self, counting non-overlapping matches.
Str.to_lowerA copy of self with every ASCII letter lowercased.
Str.to_upperA copy of self with every ASCII letter uppercased.
Str.trimA copy of self with leading and trailing ASCII whitespace removed.
Str.trim_startA copy of self with leading ASCII whitespace removed.
Str.trim_endA copy of self with trailing ASCII whitespace removed.
Str.reverseself reversed, byte by byte.
Str.repeatself repeated count times.
Str.replaceA copy of self with every occurrence of from replaced by to.
Str.pad_startself left-padded with value bytes to at least width bytes.
Str.pad_endself right-padded with value bytes to at least width bytes.
Str.splitSplits self on every occurrence of sep.
Str.split_byteSplits self on every occurrence of a single byte.
Str.split_whitespaceSplits self on runs of ASCII whitespace, discarding empty pieces.
StrList.atElement index of a string list, as a non-null string.
StrList.lenHow many strings the list holds.
StrList.is_emptyWhether the list holds no strings.
StrList.index_ofThe index of the first element equal to needle, comparing by content, or -1.
StrList.containsWhether any element equals needle, comparing by content.
StrList.non_emptyA new list holding only the non-empty elements.
Str.parse_intParses a signed integer in the given base.
Str.char_countThe number of UTF-8 code points in self.
Str.decode_utf8Decodes the UTF-8 code point beginning at index.
Str.is_valid_utf8Whether self is well-formed UTF-8.
Str.hashA 32-bit FNV-1a hash of self's bytes.

Str.len #

line 54
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;
}

Str.is_empty #

line 61
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;
}

Str.byte #

line 74
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;
}

Str.try_byte #

line 86
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;
}

Str.clone #

line 132
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;
}

Str.concat #

line 143
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;
}

Str.slice #

line 191
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;
}

Str.substr #

line 206
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);
}

Str.equals #

line 219
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;
}

Str.equals_ignore_case #

line 234
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;
}

Str.compare #

line 254
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;
}

Str.index_of #

line 276
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;
}

Str.last_index_of #

line 300
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;
}

Str.index_of_byte #

line 325
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;
}

Str.contains #

line 338
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;
}

Str.starts_with #

line 347
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;
}

Str.ends_with #

line 360
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;
}

Str.count #

line 375
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;
}

Str.to_lower #

line 393
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;
}

Str.to_upper #

line 405
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;
}

Str.trim #

line 417
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);
}

Str.trim_start #

line 433
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);
}

Str.trim_end #

line 445
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);
}

Str.reverse #

line 460
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;
}

Str.repeat #

line 478
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;
}

Str.replace #

line 501
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;
}

Str.pad_start #

line 530
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;
}

Str.pad_end #

line 544
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;
}

Str.split #

line 565
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;
}

Str.split_byte #

line 594
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;
}

Str.split_whitespace #

line 620
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;
}

StrList.at #

line 687
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();
}

StrList.len #

line 701
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;
}

StrList.is_empty #

line 708
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;
}

StrList.index_of #

line 717
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;
}

StrList.contains #

line 728
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;
}

StrList.non_empty #

line 740
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;
}

Str.parse_int #

line 772
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);
}

Str.char_count #

line 842
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;
}

Str.decode_utf8 #

line 863
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); }
  // A continuation byte cannot begin a sequence, and a 5+ byte lead never occurs in UTF-8.
  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);
}

Str.is_valid_utf8 #

line 922
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);
    }
    // Overlong, surrogate, and out-of-range checks: the three things a width-only scan
    // accepts and must not.
    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;
}

Str.hash #

line 957
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

StrA string: a garbage-collected array of bytes.
StrListA list of strings, as returned by split and consumed by join.

Str #

line 40
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.

StrList #

line 48
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.