15. Characters and code points
The character literal 'a' has type Char: one Unicode scalar value, represented as
its code point. It is an opaque type over Int (§2.7), so ==, <, hashing and a
literal pattern in a match are all Int's — but it is not an Int, 'a' + 1 does
not typecheck, and converting between the two goes through std/char: char.code(c)
gives the code point, char.of(n) builds a character from one (None if it is not a
scalar value).
use std/char
use std/str
fn is_digit(c: Char) -> Bool = c >= '0' && c <= '9'
pub fn main() -> Unit !io = {
println(to_string(is_digit('7')))
println(to_string(char.code('a')))
println(to_string(str.len("héllo 🙂")))
println(str.slice("世界你好", 0, 2))
println(from_code_points(['h', 'i']))
}
Open in playground
true
97
7
世界
hi
code_points/from_code_points go back and forth between a string and a List[Char]
(supplementary-plane emoji included), str.len counts code points, str.slice slices
by code-point index, str.at takes one Char, and str.from_char turns one Char
into a string. "${c}" is that same one-character string: std/char writes an
impl Display[Char], and Display is the top-level rendering. Show, the nested one,
is still the target type's, so a Char inside a list prints as its code-point number.
A function that indexes by code point counts from the front of the string every time
(O(n) once, O(n²) inside a loop). To scan a string, use std/cursor: a cursor is an
opaque position with a constant cost per step; arithmetic on one is a compile error,
while comparing two (==, <) is allowed.
use std/cursor
pub fn main() -> Unit !io = {
let s = "a🎈b"
let c = cursor.next(s, cursor.start(s))
println("${cursor.char(s, c)}")
println(cursor.slice(s, c, cursor.end(s)))
}
Open in playground
127880
🎈b
One step is one character: an emoji's surrogate pair is never split down the middle.
cursor.char answers an Int rather than a Char, because at the end it has to answer
-1 — a sentinel that is not a character has no home in a type where every value is one
(spec §4.8). cursor.find(s, sub, from) returns an Option[Cursor], and
cursor.skip(s, c, sub) steps over a literal already known to occur there.