Generated from /std/string.deed and the module's own tests.
The string operations that are missing from the prelude on purpose.
design/02-syntax.md has said for a long time that slicing, searching, case
and padding are missing and that they want a standard library. All four are
here. They are not in the prelude because of the rule that decides what
goes there: a thing that can be written in Deed is written in Deed, and
every function below is. trim is in the prelude because it cannot be, and
contains is not because it can.
Case (to_upper, to_lower, #672) is a table, the twenty-six ASCII
letters and nothing else, the same limit design/02-syntax.md states for
the reason a name this short may not carry a locale table a reader of the
signature cannot see. A character outside that table comes back exactly as
it went in, which is a promise this file keeps on purpose: text in a
script that has no case, or a script whose case rules do not fit a
one-character-to-one-character table (German ß, Turkish dotted and
dotless i, anything where one character becomes two), is left alone
rather than silently mangled by a rule that was not written for it. A
program that needs the real definition needs a name of its own, the same
answer this file gives about Unicode whitespace.
Everything walks. slice is a fold over the characters, index_of is a
split, and a program that holds enough text for that to matter wants a
different representation rather than a different language. That is the same
answer std/table gives about lookups.
The characters from from up to but not including to.
Out of range rather than an error, on both ends: asking for more than is
there is answered by what is there. An index is a position between
characters, so slice(text, 0, 0) and slice(text, n, n) are both empty and
neither is a mistake.
fn slice(text: String, from: Int, to: Int) -> String
none
pure
pure
assert slice("abcdef", 1, 4) == "bcd"
assert slice("abcdef", 0, 6) == "abcdef"
assert slice("abc", 2, 2) == ""
assert slice("abc", 0, 99) == "abc"
assert slice("abc", 0 - 5, 2) == "ab"
assert slice("abc", 99, 200) == ""
Where needle starts, or the length of text when it is not there.
The length rather than a Result, because every caller of this compares the
answer with something, and the position one past the end compares the way a
missing needle should: no slice starts there and no prefix reaches it.
fn index_of(text: String, needle: String) -> Int
none
pure
pure
assert index_of("hello world", "world") == 6
assert index_of("hello world", "hello") == 0
assert index_of("hello", "z") == 5
Whether text begins with prefix.
An empty prefix matches everything. A prefix longer than the text does not.
fn starts_with(text: String, prefix: String) -> Bool
none
pure
pure
assert starts_with("hello", "he") == true
assert starts_with("hello", "lo") == false
assert starts_with("hello", "") == true
Whether text ends with suffix.
An empty suffix matches everything. A suffix longer than the text does not.
fn ends_with(text: String, suffix: String) -> Bool
none
pure
pure
assert ends_with("hello", "lo") == true
assert ends_with("hello", "he") == false
Whether needle appears anywhere in text.
One comparison on a split, which is what design/02-syntax.md already names as the reason this is not a prelude function: it can be written here.
fn contains(text: String, needle: String) -> Bool
none
pure
pure
assert contains("hello world", "world") == true
assert contains("hello world", "planet") == false
assert contains("aaa", "aa") == true
assert contains("", "x") == false
Every from replaced by to. Split and join are already inverse on the
pieces they make, so this is the same walk written once.
fn replace(text: String, from: String, to: String) -> String
none
pure
pure
assert replace("a-b-c", "-", "/") == "a/b/c"
assert replace("hello", "l", "L") == "heLLo"
assert replace("hello", "z", "Z") == "hello"
assert replace("", "a", "b") == ""
text in a column width wide, or text when it is already wider.
Wider is not an error. The call that wants this is building a report, and a
report with one long name in it should come out crooked rather than not come
out. repeat already answers a negative count with an empty list, which is
the same decision one layer down.
These two are written in the order design/02-syntax.md reads them out, and the check on that sentence holds the order rather than the membership. A paragraph that lists what a module has is read down the page against the module, so the two orders being the same is the whole of what makes it readable; a set comparison passes on a sentence nobody could follow.
fn pad_left(text: String, width: Int) -> String
none
pure
pure
assert pad_left("ab", 5) == " ab"
assert pad_left("abcdef", 3) == "abcdef"
The same padding, on the right instead of the left.
Wider is still quiet: text already past width is returned unchanged.
fn pad_right(text: String, width: Int) -> String
none
pure
pure
assert pad_right("ab", 5) == "ab "
assert pad_right("abcdef", 3) == "abcdef"
text with the whitespace at the front gone.
The whitespace table is the same four characters trim uses: space, tab,
carriage return and line feed, not Unicode whitespace.
fn trim_start(text: String) -> String
none
pure
pure
assert trim_start(" hi ") == "hi "
assert trim_start("hi") == "hi"
assert trim_start(" ") == ""
assert trim_start("") == ""
The same, from the other end. One forward pass: the run resets at every non-space character, so what it holds at the end is exactly the run touching the end of the string.
fn trim_end(text: String) -> String
none
pure
pure
assert trim_end(" hi ") == " hi"
assert trim_end("hi") == "hi"
assert trim_end(" ") == ""
assert trim_end("") == ""
Case conversion is a character-code table, not a rewrite: what a character
becomes depends on what it is, not on anything the walk that reads it
knows. index_of on the two alphabets below is that table, written the
only way this language can write one today.
The table is exactly a-z and A-Z. A character not in it, accented,
non-Latin, or anything else, comes back unchanged rather than guessed at:
this is the honest limit of a table with twenty-six entries, stated rather
than left for a caller to discover (#672).
fn to_upper(text: String) -> String
none
pure
pure
assert to_upper("Hello, World! 123") == "HELLO, WORLD! 123"
assert to_upper("") == ""
assert to_upper("café") == "CAFé"
The same table as to_upper, the other direction. Same limit: a character
outside a-z/A-Z is returned as it was given.
fn to_lower(text: String) -> String
none
pure
pure
assert to_lower("Hello, World! 123") == "hello, world! 123"
assert to_lower("ALREADY") == "already"
assert to_lower("CAFé") == "café"