str.oak
← Standard library
See on GitHub ↗
{
default: default
slice: slice
take: take
takeLast: takeLast
reduce: reduce
} := import('std')
fn checkRange(lo, hi) fn checker(c) {
p := codepoint(c)
lo <= p & p <= hi
}
fn upper?(c) c >= 'A' & c <= 'Z'
fn lower?(c) c >= 'a' & c <= 'z'
fn digit?(c) c >= '0' & c <= '9'
fn space?(c) if c {
' ', '\t', '\n', '\r', '\f' -> true
_ -> false
}
fn letter?(c) upper?(c) | lower?(c)
fn word?(c) letter?(c) | digit?(c)
fn join(strings, joiner) {
joiner := default(joiner, '')
if len(strings) {
0 -> ''
_ -> strings |> slice(1) |> reduce(strings.0, fn(a, b) a + joiner + b)
}
}
fn startsWith?(s, prefix) s |> take(len(prefix)) = prefix
fn endsWith?(s, suffix) s |> takeLast(len(suffix)) = suffix
fn _matchesAt?(s, substr, idx) if len(substr) {
0 -> true
1 -> s.(idx) = substr
_ -> {
max := len(substr)
fn sub(i) if i {
max -> true
_ -> if s.(idx + i) {
substr.(i) -> sub(i + 1)
_ -> false
}
}
sub(0)
}
}
fn indexOf(s, substr) {
max := len(s) - len(substr)
fn sub(i) if _matchesAt?(s, substr, i) {
true -> i
_ -> if i < max {
true -> sub(i + 1)
_ -> -1
}
}
sub(0)
}
fn rindexOf(s, substr) {
max := len(s) - len(substr)
fn sub(i) if _matchesAt?(s, substr, i) {
true -> i
_ -> if i > 0 {
true -> sub(i - 1)
_ -> -1
}
}
sub(max)
}
fn contains?(s, substr) indexOf(s, substr) >= 0
fn cut(s, sep) if idx := indexOf(s, sep) {
-1 -> [s, '']
_ -> [
s |> slice(0, idx)
s |> slice(idx + len(sep))
]
}
fn lower(s) s |> reduce('', fn(acc, c) if upper?(c) {
true -> acc << char(codepoint(c) + 32)
_ -> acc << c
})
fn upper(s) s |> reduce('', fn(acc, c) if lower?(c) {
true -> acc << char(codepoint(c) - 32)
_ -> acc << c
})
fn _replaceNonEmpty(s, old, new) {
lold := len(old)
lnew := len(new)
fn sub(acc, i) if _matchesAt?(acc, old, i) {
true -> sub(
slice(acc, 0, i) + new + slice(acc, i + lold)
i + lnew
)
_ -> if i < len(acc) {
true -> sub(acc, i + 1)
_ -> acc
}
}
sub(s, 0)
}
fn replace(s, old, new) if old {
'' -> s
_ -> _replaceNonEmpty(s, old, new)
}
fn _splitNonEmpty(s, sep) {
coll := []
lsep := len(sep)
fn sub(acc, i, last) if _matchesAt?(acc, sep, i) {
true -> {
coll << slice(acc, last, i)
sub(acc, i + lsep, i + lsep)
}
_ -> if i < len(acc) {
true -> sub(acc, i + 1, last)
_ -> coll << slice(acc, last)
}
}
sub(s, 0, 0)
}
fn split(s, sep) if sep {
?, '' -> s |> reduce([], fn(acc, c) acc << c)
_ -> _splitNonEmpty(s, sep)
}
fn _extend(pad, n) {
times := int(n / len(pad))
part := n % len(pad)
fn sub(base, i) if i {
0 -> base << slice(pad, 0, part)
_ -> sub(base << pad, i - 1)
}
sub('', times)
}
fn padStart(s, n, pad) if len(s) >= n {
true -> s
_ -> _extend(pad, n - len(s)) << s
}
fn padEnd(s, n, pad) if len(s) >= n {
true -> s
_ -> s + _extend(pad, n - len(s))
}
fn _trimStartSpace(s) {
fn subStart(i) if space?(s.(i)) {
true -> subStart(i + 1)
_ -> i
}
firstNonSpace := subStart(0)
slice(s, firstNonSpace)
}
fn _trimStartNonEmpty(s, prefix) {
max := len(s)
lpref := len(prefix)
fn sub(i) if i < max {
true -> if _matchesAt?(s, prefix, i) {
true -> sub(i + lpref)
_ -> i
}
_ -> i
}
idx := sub(0)
slice(s, idx)
}
fn trimStart(s, prefix) if prefix {
'' -> s
? -> _trimStartSpace(s)
_ -> _trimStartNonEmpty(s, prefix)
}
fn _trimEndSpace(s) {
fn subEnd(i) if space?(s.(i)) {
true -> subEnd(i - 1)
_ -> i
}
lastNonSpace := subEnd(len(s) - 1)
slice(s, 0, lastNonSpace + 1)
}
fn _trimEndNonEmpty(s, suffix) {
lsuf := len(suffix)
fn sub(i) if i > -1 {
true -> if _matchesAt?(s, suffix, i - lsuf) {
true -> sub(i - lsuf)
_ -> i
}
_ -> i
}
idx := sub(len(s))
slice(s, 0, idx)
}
fn trimEnd(s, suffix) if suffix {
'' -> s
? -> _trimEndSpace(s)
_ -> _trimEndNonEmpty(s, suffix)
}
fn trim(s, part) s |> trimStart(part) |> trimEnd(part)