Le stringhe
Leggere, dividere e unire stringhe, distinguere byte e caratteri, e mettere un valore dentro una stringa.
Uno str contiene testo UTF-8, una codifica in cui un carattere può occupare più byte. Una stringa non cambia dopo la creazione. len() misura la lunghezza in byte e anche gli indici di slice si riferiscono ai byte.
L’indice ti dà un byte
# Bytes, not characters: this counts the ASCII spaces.
function space_count(s: str) -> i64
seen: i64 @= 0
for i in range(from: 0, to: s.len())
if s[i] == ' '
seen @ seen + 1
return seen
s[i] ti dà un u8, cioè un numero da 0 a 255,
perché un byte è quello. Viene confrontato con ' ' fra apici
singoli, che è il modo di scrivere un carattere singolo e vale anche lui come
numero. Con le virgolette doppie avresti una stringa, e le due cose non si
mescolano:
$ heroes check strings.hero
error[type_mismatch]: expected `u8`, found `str`
at strings.hero:12:12
|
12 | if s[i] == " "
| ^^^^^^^^^^^
Heroes usa tipi diversi per numeri e stringhe e non li converte automaticamente. Il confronto dell’esempio è quindi un errore di compilazione.
chars() ti dà i caratteri
# Characters, not bytes: this one is right for text.
function letters_of(s: str) -> [str]
out: [str] @= []
for c in s.chars()
if c != " "
out @ out.push(c)
return out
chars() percorre caratteri interi e te li restituisce uno per
uno come str di un carattere: ecco perché questa funzione
confronta con " " fra virgolette doppie mentre quella di prima
usava ' '. Il ciclo sui byte va bene per un protocollo o per uno
scanner; chars() va bene per qualunque cosa leggerà una persona.
La sintassi rende esplicita la scelta tra byte e caratteri.
Se una porzione di stringa taglia un carattere, il programma si ferma e segnala l’errore. In questo modo non produce testo UTF-8 non valido.
Dividere il testo in parole
# Splitting on a single space, written out. There is no library to hide in.
function words_of(s: str) -> [str]
out: [str] @= []
start: i64 @= 0
for i in range(from: 0, to: s.len())
if s[i] == ' '
if i > start
out @ out.push(s.slice(from: start, to: i))
start @ i + 1
if s.len() > start
out @ out.push(s.slice(from: start, to: s.len()))
return out
Heroes non include una funzione split. Questo esempio divide il testo in corrispondenza degli spazi. Il controllo if i > start evita di aggiungere parole vuote quando due spazi sono consecutivi.
slice(from:, to:) lavora in byte, con to escluso, e
nomina tutti e due gli estremi perché sono dello stesso tipo, la
stessa regola per cui range nomina i suoi.
Rimetterle insieme
# `join` is the other direction, and the separator is an argument, not a
# convention.
function sentence_of(words: [str]) -> str
return words.join(" ")
function initials_of(words: [str]) -> str
out: str @= ""
for w in words
if w.len() > 0
out @ out + w.slice(from: 0, to: 1)
return out
join costruisce il risultato in una sola passata. Il ciclo di concatenazione crea invece una nuova stringa a ogni iterazione e ricopia anche il testo già accumulato. La differenza diventa rilevante con stringhe lunghe. join e repeat evitano queste copie ripetute.
Testo con dei buchi dentro
Una stringa può portare dei valori. Una f prima della virgoletta d’apertura trasforma {n} nel testo di n, scritto come lo scriverebbe to_str. In un buco può stare qualsiasi espressione, e il buco finisce alla graffa che lo chiude, così una chiamata o una ricerca al suo interno si legge come si leggerebbe ovunque. {{ e }} scrivono ciascuna una graffa, e una } da sola è un errore: una graffa intesa come testo si raddoppia sempre. Una stringa senza la f tiene le sue graffe come testo, tranne dove formerebbero un buco che nomina solo ciò che esiste in quel punto, come in "{i + 1}": lì è rifiutata come una f dimenticata, e le graffe intese come testo si scrivono allora {{ e }} in una stringa con la f.
function line(at: i64, of: i64, star: Star) -> str
return f"{at}/{of} {star.name} at magnitude {star.magnitude}"
In un buco può stare ciò che to_str sa rendere: un intero, un numero con la virgola, un bool o una str. Un record o un array non hanno un testo proprio, e un valore che potrebbe mancare va prima reso certo con .must(). Il compilatore rifiuta il buco e la sua nota dice cosa scrivere al suo posto. L’esempio intero →
$ heroes run examples/gallery/12-interpolation.hero
1/2 Sirius at magnitude -1.46
2/2 Vega at magnitude 0.03
seen Sirius 2 times, {like this}
nested: 2 stars
Gli escape disponibili
Dentro una stringa: \n, \t, \r,
\\, \". Dentro un letterale di carattere,
\' prende il posto dell’ultimo. Un escape diverso produce un errore e un suggerimento di correzione:
$ heroes check strings.hero
error[unknown_escape]: `\d` is not an escape sequence — the escapes are \n \t \r \\ \"; write `\\` for a literal backslash
at strings.hero:45:24
|
45 | return words.join("\d")
| ^^
fix (certain): escape the backslash: `\\d`
Heroes segnala gli escape sconosciuti, cioè le sequenze con una barra inversa che non riconosce. Per inserire una barra inversa letterale devi raddoppiarla. Nell’esempio il compilatore suggerisce \\d e può applicare la correzione automaticamente.
Il programma
function main()
words = words_of("the quick brown fox")
print(sentence_of(words))
print(initials_of(words))
$ heroes run examples/gallery/07-strings.hero
the quick brown fox
tqbf
Poi: Le mappe, dove cercare una chiave ti restituisce un valore oppure un errore, e non c’è nessun null da dimenticare. Torna all’elenco dei capitoli. ← Prima: Generici e valori funzione
Esegui un programma tuo: bastano quattro comandi. Oppure prendi tutto il linguaggio in un file solo, e tutti i programmi già scritti.