String operations
Strings are immutable. len(text) returns the number of bytes, and text[i]
returns the byte at an integer index as a char. Bounds are checked at runtime.
The current behavior is byte-oriented rather than Unicode-code-point-oriented.
slice(text, start, end) returns a new string using a half-open range: start
is included and end is excluded. Invalid ranges terminate the program with a
runtime error.
Two strings can be joined with +. == and != compare their contents rather
than their addresses. += provides the assignment form of concatenation:
str prefix = slice("cuarzo", 0, 3)
str name = prefix + "rzo"
name += "!"
print(name == "quarzo!")join(parts, separator) concatenates a str[], inserting the separator only
between elements; joining an empty array returns "". It builds the result in
one allocation, so collecting pieces in an array and joining once is the
efficient way to build large text.
f-strings
A string written with f before it holds values in braces:
str name = "Ana"
f64 height = 1.6789
print(f"{name} is {height:2} m tall and {len(name)} letters long")
// Ana is 1.68 m tall and 3 letters longEach {expression} becomes to_str(expression), and {expression:N}
becomes to_str(expression, N), with N decimals. Any other spec after the
: becomes format(expression, "spec"), so {price:>10,.2f} is the price
with two decimals and thousands separated by commas, to the right of ten
columns. The expression can be any expression, including calls and strings
of its own. {{ and }} stand for the braces themselves. The values are
evaluated from left to right, and the result is a new string.
format(value, spec) lays out a number or any value to_str takes. A spec
is [[fill]align][sign][#][0][width][grouping][.precision][type], as in
Python:
| Part | Meaning |
|---|---|
<, >, ^ |
to the left, right, or center of the width; numbers go right and other values left unless told |
= |
numbers only: the fill between the sign and the digits |
| fill | any one character before the alignment, as *^9; a space unless given |
+, -, space |
a sign for every number, for negative ones only (the default), or a space before positive ones |
# |
0x, 0X, 0o, or 0b before the digits of x, X, o, and b, and a point kept by f, e, and g |
0 |
numbers padded with zeros after the sign, as 08 |
| width | the least number of characters |
, or _ |
the thousands separated, or for _ with x, o, and b each four digits |
.precision |
the decimals of f, e, and %, the significant digits of g, and the most characters of text |
| type | d whole numbers; x, X, o, b hexadecimal, octal, and binary; f, e, E, g, G fixed, scientific, and either, % a percentage; s text |
print(format(1234567, ","), format(255, "#x"), format(0.256, ".1%")) // 1,234,567 0xff 25.6%
print(f"[{42:>6}] [{42:<6}] [{"ok":*^6}] [{-3.14159:+.2f}] [{1e6:.2e}]")
// [ 42] [42 ] [**ok**] [-3.14] [1.00e+06]A float without a type is written as to_str writes it, or with
precision decimals when one is given; a whole number with e, f, g,
or % is written as a float. Widths count characters. A spec written in
the code is checked when the program is compiled; one made while it runs
and not valid stops it with an error.
to_str(value) returns exactly the text that print(value) would write for
an integer, float, bool, char, str, or enum value:
str[] parts = ["n=", to_str(42), " ok=", to_str(true)]
print(join(parts, "")) // n=42 ok=trueText built-ins
These built-ins search and edit text. Strings never change, so the editing ones return a new string:
| Built-in | Result | Behavior |
|---|---|---|
split(text, separator) |
str[] |
the pieces between each separator |
find(text, part) |
i64 |
the index of the first part, or -1 |
find(text, part, start) |
i64 |
the same, searching from start |
replace(text, old, new) |
str |
text with every old replaced by new |
trim(text) |
str |
text without spaces, tabs, or line ends at either end |
starts_with(text, prefix) |
bool |
whether text begins with prefix |
ends_with(text, suffix) |
bool |
whether text ends with suffix |
upper(text), lower(text) |
str |
ASCII letters in upper or lower case |
parse_int(text) |
Result[int] |
a whole decimal integer, with an optional sign |
parse_float(text) |
Result[f64] |
a decimal number such as -0.5 or 1.5e3 |
ord(c) |
int |
the byte code of a char, from 0 to 255 |
chr(code) |
char |
the char with a byte code from 0 to 255 |
str[] fields = split("ana, 31, Rosario", ", ")
print(len(fields), upper(fields[0])) // 3 ANA
print(find("banana", "na"), find("banana", "na", 3)) // 2 4
print(chr(ord('a') + 1)) // bparse_int and parse_float accept the whole text and nothing else: spaces,
hexadecimal, and words such as inf are errors, as is an integer that does
not fit in an int. They return an error Result with a
message such as '12abc' is not an integer.
split keeps empty pieces, so split("a,,b", ",") has three elements and
split("", ",") has one. An empty separator for split, an empty old for
replace, a find start outside the text, and a chr code outside 0 to 255
are runtime errors.
Concatenation, slicing, joining, and conversion allocate new strings, which the collector reclaims once nothing refers to them. An indexed string assignment is rejected because strings are immutable.