sabato 18 aprile 2015

Corso Lua - puntata 7 - La libreria standard


La libreria standard di Lua


Eccoci giunti al grande capitolo della libreria standard di Lua. Si tratta di una collezione di funzioni utili a svolgere compiti ricorrenti su stringhe, file, tabelle, ecc, e si trovano precaricate in una serie di tabelle e quindi immediatamente utilizzabili.
L'elenco completo ma in ordine sparso con il nome della tabella/modulo contenitore e la descrizione applicativa è il seguente:
  • math: matematica;
  • table: utilità sulle tabelle;
  • string: ricerca, sostituzione e pattern matching;
  • io: input/output facility, operazioni sui file;
  • bit32: operazioni bitwise (solo in Lua 5.2)
  • os: date e chiamate di sistema;
  • coroutine: creazione e controllo delle coroutine;
  • utf8: utilità per testo in codifica Unicode UTF-8 (da Lua 5.3)
  • package: caricamento di librerie esterne;
  • debug: accesso alle variabili e performance assessment.

La pagina web a questo indirizzo fornisce tutte le informazioni di dettaglio sulla libreria standard di Lua 5.2.

Libreria matematica

Nella libreria memorizzata nella tabella 'math' ci sono le funzioni trigonometriche 'sin', 'cos', 'tan', 'asin' eccetera --- che come di consueto lavorano in radianti, le funzioni esponenziali 'exp', 'log', 'log10', quelle di arrotondamento 'ceil', 'floor' e quelle per la generazione pseudocasuale di numeri come 'random', 'randomseed', eccetera. Oltre alle funzioni citate la tabella include campi numerici come la costante π.

Un esempio introduttivo:
print(math.pi)
print(math.sin( math.pi/2 ))
print(math.cos(0))

-- accorciamo i nomi delle funzioni ;-)
local pi, sin, cos = math.pi, math.sin, math.cos
local square = function (x) return x*x end
local function one(a)
    return square(sin(a))+square(cos(a))
end

for i=0, 1, 0.1 do
    print(i, one(i))
end

Notate come nella funzione one() entra in azione la closure --- introdotta nella puntata precedente --- perché in essa si fa uso di funzioni definite nell'ambiente esterno rispetto al corpo della funzione stessa. Ciò conferma quanto sia naturale utilizzare questa potente caratteristica di Lua anche con riferimenti a funzione.

Libreria stringhe

In Lua non è infrequente elaborare grandi porzioni di testo.
La libreria per le stringhe è memorizzata nella tabella 'string' ed è una delle più utili.
Con essa si possono formattare campi e compiere operazioni di ricerca e sostituzione.

string.format()

La funzione più semplice è quella di formattazione string.format(). Essa restituisce una stringa prodotta con il formato fornito come primo argomento riempito con i dati forniti dal secondo argomento in poi.

Il formato è esso stesso specificato come una stringa contenente dei campi creati con il simbolo percentuale e uno specificatore di tipo. Per esempio "%d" indica il formato relativo a un numero intero, dove 'd' sta per digit mentre "%f" indica il segnaposto per un numero decimale --- 'f' sta per float.

I campi formato derivano da quelli della funzione classica di libreria printf() del C. Di seguito un codice di esempio:

-- "%d" means digits
local s1 = string.format("%d", 456.18)
print(s1)
local s01 = string.format("%05d", 456.18)
print(s01)

-- "%f" means float
local num = 123.456
local s2 = string.format("intero: %d decimale: %0.2f", num, num)

-- "%s" means string
local s3 = string.format("s1='%s', s2='%s'", s1, s2)
print(s3)

local s4 = string.format("%24s", "pippo")
print(s4)

Come avete potuto notare nel codice di esempio, è anche possibile fornire un ulteriore specifica di dettaglio tra il '%' e lo specificatore di formato, per esempio per gestire il numero delle cifre decimali.

Pattern

Per elaborare il testo si utilizza di solito una libreria per le espressioni regolari. Lua mette a disposizione alcune funzioni di sostituzione e "pattern matching" meno complete dell'implementazione dello standard POSIX per le espressioni regolari ma molto spesso più semplici da utilizzare.

Esistono due strumenti di base, il primo è il pattern e il secondo è la capture.

Il pattern è una stringa che può contenere campi chiamati classi simili a quelli per la funzione di formato vista in precedenza che stavolta però si riferiscono al singolo carattere, e questa differenza è essenziale.

La funzione di base che accetta pattern è string.match() che restituisce la prima corrispondenza trovata nella stringa primo argomento che corrisponde al pattern dato come secondo argomento.

Possiamo ricercare in un numero di tre cifre all'interno di un testo con il pattern "%d%d%d":
-- semplice pattern in azione
local s = "le prime tre cifre decimali di π = 3,141592654 sono"
local pattern = "%d%d%d"
print(string.match(s, pattern))

Le classi carattere possibili sono le seguenti:
  • . un carattere qualsiasi;
  • %a una lettera;
  • %c un carattere di controllo;
  • %d una cifra;
  • %l una lettera minuscola;
  • %u una lettera maiuscola;
  • %p un carattere di interpunzione;
  • %s un carattere spazio;
  • %w un carattere alfanumerico;
  • %x un carattere esadecimale;
  • %z il carattere rappresentato con il codice 0.

Le classi ammettono quattro modificatori per esprimere le ripetizioni dei caratteri:
  • '+' indica 1 o più ripetizioni;
  • '*' indica 0 o più ripetizioni;
  • '-' come '*' ma nella sequenza più breve;
  • '?' indica 0 o 1 occorrenza;

-- occorrenza di un numero intero
-- come una o più cifre consecutive
print(string.match("l'intero 65 interno", "%d+"))
print(string.match("l'intero 0065 interno", "%d+"))

-- e per estrarre un numero decimale?
-- il punto è una classe così dobbiamo utilizzare
-- la classe %. per ricercare il carattere '.'
print(string.match("num = 45.12 :-)", "%d+%.%d+"))

Capture

Il pattern può essere arricchito per non solo trovare corrispondenze ma per restituirne parti componenti. Questa funzionalità viene chiamata capture e consiste semplicemente nel racchiudere nel pattern tra parentesi tonde le sequenze di caratteri ricercati.

Per esempio per estrarre l'anno di una data nel formato 'dd/mm/yyyy' possiamo usare il pattern con la capture seguente "%d%d/%d%d/(%d%d%d%d)":
-- extract only

local s = "This '10/03/2025' is a future date"
print(string.match(s, "%d%d/%d%d/(%d%d%d%d)"))

Più capture nel pattern daranno altrettanti argomenti multipli di uscita:
-- extract all
local s = "This '10/03/2025' is a future date"
local d, m, y = string.match(s, "(%d+%d?)/(%d%d)/(%d%d%d%d)")
print(d, m, y)

string.gsub()

Abbiamo appena cominciato a scoprire le funzionalità dedicate al testo disponibili nella libreria standard di Lua precaricata nell'interprete.

Diamo solo un altro sguardo alla libreria presentando la funzione string.gsub(). Il suo nome sta per global substitution, ovvero sostituisce tutte le occorrenze in un testo.

Intanto per individuare le occorrenze è naturale pensare di utilizzare un pattern e che sia possibile utilizzare le capture nel testo di sostituzione, per esempio:

local s = "The house is black."
print(string.gsub(s, "black", "red"))
print(string.gsub(s, "(%a)lac(%a)", "%2lac%1"))

Il primo argomento è la stringa da ricercare, il secondo è il pattern e il terzo è il testo di sostituzione dell'occorrenza ma può essere una tabella dove le chiavi saranno corrispondenti al pattern saranno sostituite con i rispettivi valori, oppure anche una funzione che riceverà le catture e calcolerà il testo da sostituire.

Una funzione quindi assai flessibile. Mi viene in mente questo esercizio: moltiplicare di 10 tutti gli interi in una stringa, ed ecco il codice:

local s = "Cose da fare oggi 5, cosa da fare domani 2"
print(string.gsub(s, "%d+", function(n)
    return tonumber(n)*10
end))

A questo punto degli esempi avrete certamente capito che 'gsub()' restituisce anche il numero delle sostituzioni effettuate.

Tutte queste funzioni restituiscono una stringa costruita ex-novo e non modificano la stringa originale di ricerca.

Esercizi


1 - Qual è la differenza tra i campi di formato della funzione string.format() e le classi dei pattern? Quali le somiglianze?

2 - Stampare una data nel formato 'dd/mm/yyyy' a partire dagli interi contenuti nelle variabili d, m e y.

3 - Cosa restituisce l'esecuzione della funzione
string.match("num = .123456 :-)", "%d+%.%d+")

Quale pattern corrisponde a un numero decimale la cui parte intera può essere omessa?

4 - Come estrarre dal nome di un file l'estensione?

5 - Come eliminare da un testo eventuali caratteri spazio iniziali e/o finali?

6 - Il pattern "(%d+)/(%d+)/(%d+)" è adatto per catturare giorno, mese e anno di una data presente in una stringa nel formato 'dd/mm/yyyy'?

7 - Creare un esempio che utilizzi la funzione string.gsub() e una funzione in sintassi anonima a due argomenti corrispondenti a due capture nel pattern di ricerca.


Riassunto della puntata

In Lua sono immediatamente disponibili un bel gruppo di funzioni che ne formano la libreria standard. In questa puntata abbiamo esplorato in particolare la libreria dedicata al testo lasciando all'utente di completare l'argomento sui testi di riferimento del linguaggio.

La prossima puntata sarà dedicata al generic for il costrutto di Lua per iterare collezioni di dati.

Nessun commento:

Posta un commento