strings

String manipulation: splitting, joining, trimming, case, search, padding.

Oxigen
introduce strings

Strings are immutable — every function here returns a new string.

FunctionSignatureReturns
splitsplit(s, delim)array
joinjoin(arr, delim)str
lineslines(s)array
trimtrim(s)str
stripstrip(s, chars)str
strip_leftstrip_left(s, chars)str
strip_rightstrip_right(s, chars)str
upperupper(s)str
lowerlower(s)str
capitalizecapitalize(s)str
replacereplace(s, old, new)str
starts_withstarts_with(s, prefix)bool
ends_withends_with(s, suffix)bool
contains_strcontains_str(s, sub)bool
containscontains(s, sub)bool
index_ofindex_of(s, sub, start?)int (-1 if absent)
countcount(s, sub)int
sliceslice(s, start, end)str
char_atchar_at(s, i)str
is_emptyis_empty(s)bool
byte_lenbyte_len(s)int
reversereverse(s)str
repeatedrepeated(s, n)str
pad_leftpad_left(s, width, fill)str
pad_rightpad_right(s, width, fill)str

#Splitting and joining

#split(s, delim)

Split on a literal delimiter. Returns an array of strings.

Oxigen
println(strings.split("a,b,c", ","))       // [a, b, c]
println(strings.split("one two", " "))     // [one, two]

Destructuring the result is the usual way to take a key/value pair apart:

Oxigen
key, value := strings.split("PORT=8080", "=")
println(key, value)   // PORT 8080

For a pattern delimiter, use regex.split.

#join(arr, delim)

Join an array of strings with a delimiter — the inverse of split.

Oxigen
println(strings.join(["x", "y", "z"], "-"))   // x-y-z
println(strings.join(["a", "b"], ""))         // ab

#lines(s)

Split on newlines. Shorthand for split(s, "\n").

Oxigen
introduce io
introduce strings

each line in strings.lines(io.read_file("notes.txt")) {
    println("> " + line)
}

#Trimming

#trim(s)

Remove leading and trailing whitespace.

Oxigen
println("[" + strings.trim("  hi  ") + "]")   // [hi]

#strip(s, chars)

Remove any of the characters in chars from both ends. chars is a set of characters, not a substring.

Oxigen
println(strings.strip("##head##", "#"))      // head
println(strings.strip("  --test--  ", " -")) // test

#strip_left(s, chars)

Same, left end only.

Oxigen
println(strings.strip_left("--value", "-"))   // value

#strip_right(s, chars)

Same, right end only.

Oxigen
println(strings.strip_right("value--", "-"))   // value

#Case

#upper(s)

Oxigen
println(strings.upper("hi"))   // HI

#lower(s)

Oxigen
println(strings.lower("HI"))   // hi

#capitalize(s)

Uppercase the first character only; the rest is untouched. An empty string comes back unchanged.

Oxigen
println(strings.capitalize("hello world"))   // Hello world
println(strings.capitalize(""))              //

#Search and replace

#replace(s, old, new)

Replace every occurrence of old.

Oxigen
println(strings.replace("foo bar bar", "bar", "baz"))   // foo baz baz

For a pattern, use regex.replace.

#starts_with(s, prefix)

Oxigen
println(strings.starts_with("hello", "he"))   // True

#ends_with(s, suffix)

Oxigen
println(strings.ends_with("hello", "lo"))   // True

#contains_str(s, sub) / contains(s, sub)

True if sub appears anywhere in s. The two names are the same function — contains is the shorter alias.

Oxigen
println(strings.contains_str("hello", "ell"))   // True
println(strings.contains("hello", "z"))         // False

#index_of(s, sub, start?)

Index of the first occurrence of sub at or after index start (default 0), or -1 if there is none. Indexes count characters, like s[i], not bytes.

Oxigen
println(strings.index_of("hello", "ll"))   // 2
println(strings.index_of("hello", "z"))    // -1
println(strings.index_of("héllo", "l"))    // 2
println(strings.index_of("a-b-c", "-", 2)) // 3

To find every occurrence, start each search just past the last one:

Oxigen
s := "a-b-c"
i := strings.index_of(s, "-")
repeat when i != -1 {
    println(i)                             // 1, then 3
    i = strings.index_of(s, "-", i + 1)
}

#count(s, sub)

Number of non-overlapping occurrences.

Oxigen
println(strings.count("banana", "an"))   // 2
println(strings.count("aaaa", "aa"))     // 2   <- non-overlapping
println(strings.count("abc", ""))        // 0

#Slicing and characters

#slice(s, start, end)

Substring from start up to (not including) end. Equivalent to the s[start:end] operator. An end past the string length is clamped.

Oxigen
println(strings.slice("hello world", 0, 5))   // hello
println(strings.slice("abc", 0, 99))          // abc

#char_at(s, i)

The character at index i, as a one-character string.

Oxigen
println(strings.char_at("hello", 1))   // e

#is_empty(s)

True when the length is 0.

Oxigen
println(strings.is_empty(""))    // True
println(strings.is_empty("x"))   // False

#byte_len(s)

Length in UTF-8 bytes. len(s) counts characters, which is what indexing and slicing use; byte counts are for wire formats such as Content-Length.

Oxigen
println(len("héllo"))               // 5
println(strings.byte_len("héllo"))  // 6

#Building

#reverse(s)

Reverse the characters.

Oxigen
println(strings.reverse("hello"))   // olleh

#repeated(s, n)

s repeated n times. It is not called repeat because that is a loop keyword.

Oxigen
println(strings.repeated("ab", 3))   // ababab
println(strings.repeated("-", 20))   // --------------------

#pad_left(s, width, fill)

Prepend fill until the string is at least width characters long. A string already at least that long is returned unchanged. An empty fill with a string shorter than width is an error (it could never reach the width).

Oxigen
println(strings.pad_left("7", 3, "0"))   // 007

#pad_right(s, width, fill)

Append fill until the string is at least width long — the way to line up columns.

Oxigen
println(strings.pad_right("7", 3, "."))   // 7..

fill may be more than one character, in which case the result can overshoot width.


#Worked example

Oxigen
introduce strings

// Parse "key: value" config lines into a map, ignoring blanks and comments.
fun parse_config(text <str>) {
    out <map> := {}
    each line in strings.lines(text) {
        trimmed := strings.trim(line)
        option {
            strings.is_empty(trimmed) -> { None }
            strings.starts_with(trimmed, "#") -> { None }
            strings.contains(trimmed, ":") -> {
                k, v := strings.split(trimmed, ":")
                out[strings.trim(k)] = strings.trim(v)
            }
        }
    }
    out
}

println(parse_config("# settings\nhost: localhost\nport: 8080\n"))
// {host: localhost, port: 8080}

See also: regex for pattern matching, encoding for base64/hex/URL escaping, and the global builtins len, str, int, float, chars in builtins.md.