std.string

String manipulation lives at std.string. A subset of these is also
available as self-operator pipes on strings (e.g. s:upper()) — the seeded
string pipes are upper lower trim trim_left trim_right substr replace replace_first repeat pad_left pad_right plus the dispatch pipes insert erase reverse. See Pipes for the full picture.

Reference

upper(s)  lower(s)  trim(s)  trim_left(s)  trim_right(s)
substr(s, start[, length])  char_at(s, index)
char_code(s[, index])  from_char_code(code...)
find(s, pattern)  index_of(s, pattern)  last_index_of(s, pattern)
contains(s, pattern)  starts_with(s, prefix)  ends_with(s, suffix)
count(s, pattern)
replace(s, old, new)  replace_first(s, old, new)
repeat(s, n)  reverse(s)  insert(s, index, text)  erase(s, start[, length])
pad_left(s, width[, fill])  pad_right(s, width[, fill])
split(s, delimiter[, limit])  join(array[, separator])
is_empty(s)  is_alpha(s)  is_digit(s)  is_alnum(s)
is_upper(s)  is_lower(s)  is_space(s)

find / index_of / last_index_of return an index or -1 when not found.
count returns 0 when absent. All indices are 0-based; out-of-range
substr starts throw ARRAY_INDEX_OUT_OF_RANGE, while out-of-range
char_at throws too — check len() first when indexing untrusted input.
substr’s optional third argument is a length, not an end index; when
omitted it runs to the end of the string. Defaults: char_code(s) reads
index 0; join(array) separates with ""; pad_left/pad_right fill
with " "; split(s, delim) without limit splits fully, and with a limit
returns at most that many parts with the remainder in the last element.

Case and whitespace

print(std.string.upper("hello"))       # HELLO
print(std.string.lower("HeLLo"))       # hello

print(std.string.trim("  hi  "))       # hi        (both ends)
print(std.string.trim_left("  hi  "))  # "hi  "    (leading only)
print(std.string.trim_right("  hi  ")) # "  hi"    (trailing only)

# they are pipes too, and chain:
var s = "  hello  "
print(s:trim():upper())                # HELLO

The whole trim family is commonly used to clean up captured command output —
e.g. std.string.trim(std.system("whoami").output) to strip its trailing
newline.

Searching

All searches are plain substring searches (not regular expressions):

var s = "banana"

std.string.contains(s, "na")      # true
std.string.starts_with(s, "ban")  # true
std.string.ends_with(s, "ana")    # true
std.string.count(s, "na")         # 2

std.string.find(s, "na")          # 2        (first index)
std.string.index_of(s, "na")      # 2        (same)
std.string.last_index_of(s, "na") # 4        (search from the end)
std.string.find(s, "zz")          # -1       (not found)

Picking characters and codes

var word = "abc"

std.string.char_at(word, 1)        # "b"                (1-char string)
std.string.char_code(word)         # 97                 (first char)
std.string.char_code(word, 1)      # 98                 (char at index 1)
std.string.from_char_code(72, 105) # "Hi"               (codes -> string)

from_char_code accepts any number of codes, so it pairs neatly with char_code
for character arithmetic, e.g. shifting letters, or producing a string from
returned byte codes.

Cutting, joining, and editing

substr extracts a run (length, not end index). replace swaps every
occurrence; replace_first only the first:

var s = "a-b-c"

std.string.substr(s, 1, 3)      # "-b-"    (3 chars from index 1)
std.string.substr(s, 4)         # "c"      (to the end)
std.string.replace(s, "-", "+") # "a+b+c"
std.string.replace_first(s, "-", "+")  # "a+b-c"

std.string.insert("abc", 1, "XY")   # "aXYbc"
std.string.erase("hello", 1, 2)     # "hlo"     (2 chars from index 1)
std.string.reverse("abc")           # "cba"
std.string.repeat("ab", 3)          # "ababab"

Splitting and building

std.string.split("a,b,c", ",")          # ["a", "b", "c"]
std.string.split("a,b,c,d", ",", 2)     # ["a", "b,c,d"]  (cap at 2)
std.string.join(["a", "b", "c"])        # "abc"           (no separator)
std.string.join(["a", "b"], "-")        # "a-b"

# trim + split is the classic CSV / config-line idiom
var line = " name, age, city "
var parts = std.string.split(std.string.trim(line), ",")
print(parts)                            # ["name", " age", " city"]

std.string.pad_left("7", 3, "0")        # "007"
std.string.pad_right("x", 3, ".")       # "x.."

Classification

Each is_* test returns a boolean:

std.string.is_digit("5")     # true
std.string.is_alpha("a")     # true
std.string.is_alnum("a1")    # true
std.string.is_space(" ")     # true    (newline/tab etc. too)
std.string.is_upper("A")     # true
std.string.is_lower("a")     # true
std.string.is_empty("")      # true

Use split plus every from std.sort to check an entire string:

# is every character a digit?
var pin = "1234"
var chars = std.string.split(pin, "")          # ["1", "2", "3", "4"]
print(std.sort.every(chars, fn(c) { return std.string.is_digit(c) }))  # true