Sei sulla pagina 1di 115

Mirco Baragiani

PROGRAMMAZIONE LUA
LA GUIDA COMPLETA
PER MAC, WINDOWS E LINUX
© 2014 Area 51 s.r.l., San Lazzaro di Savena (Bologna)
Prima edizione ebook Area51 Publishing: settembre 2014
Curatore della collana: Mirco Baragiani
Cover: Valerio Monego
Redazione e sviluppo ebook: Enrico De Benedictis
Se intendi condividere questo ebook con un’altra persona, ti chiediamo cortesemente di scaricare una
copia a pagamento per ciascuna delle persone a cui lo vuoi destinare. Se stai leggendo questo ebook e
non lo hai acquistato, ti chiediamo, se ti piace, di acquistarne anche una copia a pagamento, al fine di
poterci permettere di far crescere il nostro lavoro e di offrirti sempre più titoli e una qualità sempre
maggiore. Grazie per il tuo aiuto e per aver rispettato il lavoro dell’autore e dell’editore di questo
libro.
Data la rapidità con cui i tool di sviluppo e i linguaggi vengono aggiornati, i contenuti di questo
ebook si intendono fedeli allo stato dell’arte al momento della pubblicazione.

Segui Area51 Publishing su:

Google Plus

Facebook

Twitter

Pinterest
www.area51editore.com
www.area51editore.com/blog
I computer sono incredibilmente veloci, accurati e stupidi. Gli uomini
sono incredibilmente lenti, inaccurati e intelligenti. L’insieme dei due
costituisce una forza incalcolabile. (Albert Einstein)
Introduzione

LUA è un linguaggio di scripting specializzato per sistemi embedded


(mobile devices) ma utilizzato anche in ambienti desktop e industriali. Tra
le sue caratteristiche puoi riscontrare potenza, velocità e leggerezza. LUA
combina una sintassi procedurale di semplice descrizione dei dati con
potenti costrutti basati su array associativi e semantica estensibile. LUA è
dinamicamente tipizzato, esegue il codice interpretando bytecode da un
registro basato su macchina virtuale e dispone di gestione automatica della
memoria con garbage collection incrementale, che lo rende ideale per la
configurazione, lo scripting e la prototipazione rapida del software.
LUA viene progettato, realizzato e gestito da un team (PUC-Rio) della
Pontificia Università Cattolica di Rio de Janeiro in Brasile. È nato e
cresciuto nel Tecgraf, la Computer Graphics Technology Group di PUC-
Rio, ed è ora ospitato presso LabLUA. Sia Tecgraf e LabLUA sono
laboratori del Dipartimento di Informatica di PUC-Rio.

Perché scegliere LUA?

• LUA è un linguaggio robusto fortemente collaudato


LUA è stato utilizzato in molte applicazioni industriali (ad esempio, Adobe
Photoshop e Lightroom), con particolare enfasi sui sistemi integrati (ad
esempio, il middleware Ginga per la TV digitale in Brasile) e giochi (ad
esempio, World of Warcraft e Angry Birds). LUA è attualmente il
linguaggio di scripting più utilizzato al mondo per lo sviluppo di
videogiochi. Nel mercato sono presenti molti manuali di riferimento in
lingua inglese sin dalla sua creazione nel 1993.
• LUA è veloce
LUA ha una grandissima reputazione per le prestazioni. Pretendere di
essere “veloce come LUA” è un’aspirazione di moltissimi linguaggi di
scripting. Diversi benchmark mostrano LUA come il più veloce tra i
linguaggi di scripting interpretati. Programmi molto complessi e di grandi
dimensioni sono stati scritti con LUA.

• LUA è portabile
LUA è distribuito in un pacchetto di piccole dimensioni e si integra “out-of-
the-box” in tutte le piattaforme che hanno un compilatore C standard. LUA
funziona su tutte le versioni di Unix e Windows, su dispositivi mobili (con
sistema operativo Android, iOS, BREW, Symbian, Windows Phone), su
microprocessori embedded (come ARM e rabbit, per applicazioni come
Lego Mindstorms), su mainframe IBM, etc.

• LUA è embedded (incorporato)


LUA è un linguaggio con un ingombro ridotto: è quindi possibile
incorporarlo facilmente nelle tue applicazioni. LUA ha un set di API
semplici e ben documentate che consentono una forte integrazione con il
codice scritto in altri linguaggi. È semplicissimo estendere LUA con librerie
scritte in altre linguaggi, altrettanto facile il processo inverso, estendere i
programmi scritti in altri linguaggi con LUA. LUA è stato utilizzato per
estendere i programmi scritti non solo in C e C ++, ma anche in Java, C#,
Objective C, Smalltalk, Fortran, Ada, Erlang e incredibilmente anche con
altri linguaggi di scripting, come Perl e Ruby.

• LUA è potente (ma semplice)


Un concetto fondamentale nella progettazione di LUA è fornire meta-
meccanismi di attuazione caratteristica, invece di fornire una serie di
funzionalità direttamente tradotte nel linguaggio. Ad esempio, sebbene
LUA non sia un linguaggio orientato agli oggetti, fornisce meta-
meccanismi di attuazione per le classi e l’ereditarietà. Questa caratteristica
consente un’estensione della semantica nel linguaggio non convenzionale.

• LUA è molto ridotto


Aggiungere LUA a un’applicazione non aumenta di molto il peso in termini
di byte. Ad esempio la versione di LUA 5.2.1, che contiene il codice
sorgente e la documentazione, pesa 245K compressi e non compressi 960K.
Il codice sorgente sono circa 20.000 righe di C. In Linux, ad esempio,
l’interprete LUA costruito con tutte le API standard pesa 182K e la libreria
pesa 243K.

• LUA è libero
LUA è open-source, distribuito sotto la famosa licenza MIT. Esso può
essere utilizzato per qualsiasi scopo, compresi progetti commerciali, senza
alcun costo. Basta scaricarlo e usarlo.

Maggiori informazioni sul progetto LUA: http://www.lua.org/home.html


1. Installare e configurare LUA
Introduzione

Utilizzare l’interprete di LUA nel tuo computer, che tu sia un utente Mac,
Linux/Unix oppure Windows, è un’operazione semplice e immediata, ti
basta scaricare l’interprete dal sito del progetto oppure un’ambiente di
sviluppo integrato. La seconda ipotesi è la scelta che ti propongo per questo
manuale. Zerobrane Studio ha realizzato un bellissimo IDE per sviluppare
attraverso LUA, l’applicativo è disponibile per tutti i sistemi operativi e
potrai utilizzarlo gratuitamente oppure contribuendo con una donazione a
tua discrezione. LUA, come hai potuto appurare, è un linguaggio di
scripting molto potente, semplice da apprendere e leggero. Questi sono solo
alcuni motivi che ne hanno decretato il successo planetario e molti
produttori di motori di sviluppo si sono orientati a questo linguaggio
soprattutto nel campo videoludico. Attraverso l’IDE che ti propongo potrai
provare e testare tutto il codice presente in questo manuale.

Installare e configurare l’IDE

1. Apri il tuo browser preferito e quindi digita il seguente URL:


studio.zerobrane.com
2. Clicca ora sul link “Download”.
1. Seleziona un’opzione di donazione se desideri contribuire al progetto,
oppure clicca l’ultima opzione e inserisci il numero zero.
2. Clicca ora sul link indicato nella figura.

Seleziona ora il link della versione corrispondete al tuo sistema operativo e


avvia il download del pacchetto. Quindi installalo sul tuo computer
cliccando il file scaricato.
Avvia l’IDE per la prima volta e inizia a prendere confidenza con
l’ambiente.

1. Alla tua sinistra puoi notare un corposo elenco di cartelle e file con
esempi di codice realizzati attraverso LUA.
2. Al centro c’è l’editor del codice, con tutte le caratteristiche di un
moderno editor (completamento della sintassi etc.).
3. In basso puoi notare la consolle di output dove visualizzerai tutti i tuoi
risultati.

Clicca ora il link al pulsante “Project”, quindi “LUA Interpreter” se desideri


utilizzare una specifica versione dell’interprete per un ambiente di sviluppo
integrato.
Crea ora un nuovo progetto semplicemente cliccando su “File” > “New
file” > “Save As” e inserendo un nome per il file che desideri salvare
(ricorda di indicare l’estensione LUA al file), clicca quindi il pulsante
“Save”.
Sei pronto ora per scrivere il primo codice.

1. Inserisci la seguente riga di codice:


Print(“Hello world”)

2. Clicca ora due volta il pulsante verde di Run, se tutto è andato a buon
fine nella consolle vedrai visualizzata la stringa “Hello world”.
Conclusione

Installare e configurare l’IDE LUA è un’operazione semplice e immediata,


utilizza da ora in poi questo strumento per studiare e testare il codice.
2. Introduzione al linguaggio
Prefazione

LUA è uno standard industriale per la realizzazione di videogames e


moltissime tipologie di applicazioni multipiattaforma, è molto simile a
javascript e al linguaggio ActionScript di Flash; avere già esperienze di
sviluppo con i suddetti linguaggi è propedeutico all’utilizzo di LUA. Molti
sviluppatori hanno scelto LUA per la sua semplicità di apprendimento e la
sua versatilità.

Le variabili

Come tutti i linguaggi, anche LUA possiede le variabili. Puoi pensare alle
variabili come a contenitori dove memorizzare qualsiasi tipologia di
informazione: lo stato di un processo, un valore numerico, una stringa, un
oggetto etc. Memorizzare gli elementi citati precedentemente in una
variabile viene comunemente chiamato assegnamento. LUA utilizza
principalmente tre tipologie di variabili per tale operazione:

Globali
Locali
Campi di tabella

Variabili globali
Una variabile globale può essere modificata in qualsiasi parte di un
programma e il suo campo di visibilità è totale. Questo significa che è
possibile accedere a essa da qualsiasi punto del tuo programma sia in lettura
che in scrittura. Una variabile è considerata globale nel momento in cui tu
le assegni un valore. Osserva i seguenti esempi:

varialibe_esempio = 99
print (varabile_esempio)

Questo brevissimo codice (ti invito a testarlo nell’IDE attraverso la consolle


testuale) non fa altro che creare una variabile globale di nome
“variabile_esempio” e assegnarle il valore 99, la funzione print stamperà il
valore della medesima nella consolle.

Variabili locali
Le variabili locali, a differenza di quelle globali, hanno un campo di
visibilità molto più limitato e solitamente possono essere modificate oppure
lette solo da una funzione, oppure da un blocco specifico di codice. Quando
tu crei una funzione oppure un blocco di codice definito, in sostanza stai
creando un campo di esistenza locale e puoi dichiarare e inizializzare una
variabile al suo interno, la variabile avrà un ciclo di vita che si andrà a
esaurire con il contesto creato. La localizzazione delle variabile è molto
salutare per il tuo codice, essa ti aiuta a migliorare le prestazioni della tua
applicazione. Osserva il seguente esempio per comprendere come realizzare
una variabile locale:
local x = 5

Osserva ora il seguente esempio, per comprendere bene il concetto di


variabile locale:

x = 7
local k = 3

while K <= 10 do
local x = k * 3
print (x)
k = k + 1
end

print (x)

Questo segmento di codice dichiara e inizializza due variabili: una globale


(la x) e un’altra locale (la k). Il ciclo while itera 7 volte, ovvero il valore di
k che all’inizio equivale a 3 e che viene incrementato di un’unità fino a
raggiungere il valore limite di 10. All’interno del ciclo viene ridefinita la
variabile x localmente e viene utilizzata per il calcolo e la stampa a video
(l’output sarà: 6 12 15 18 21 24 27).
All’uscita del ciclo è richiesta la stampa della variabile x ed essendo il
valore globale della variabile x = 7 verrà stampato questo ultimo numero e
non il 27 (ultimo valore calcolato all’interno del ciclo). Ti invito a provare
questa porzione di codice con l’IDE e la consolle testuale.

Campi di tabelle

I campi di tabelle sono strutture composte da un valore e una chiave, avrai


modo di approfondire questi concetti nei capitoli successivi. Lo scopo di
questo paragrafo è giusto quello di darti una infarinatura iniziale su questi
strumenti di programmazione con LUA. Osserva subito il seguente esempio
per comprendere da vicino di cosa si tratta:

tabella = { str1='Ciao', str2='Mondo' }

print (tabella.str1)

print (tabella.str1,tabella.str2)
Questo frammento di codice crea una tabella chiamata “tabella” composta
da due campi, il campo str1 associato alla stringa ‘Ciao’ e il campo str2
associato alla stringa ‘Mondo’. La prima funzione print stamperà nel
terminale il valore del primo campo associato “Ciao”, la seconda print
invece stamperà entrambi i campi “Ciao” e “Mondo”.

Commentare il codice con LUA SDK

Tutti i linguaggi di scripting permettono di commentare una riga di codice


oppure un’intera porzione. Per commentare una singola riga si utilizza il
doppio trattino (--). Esempio:
local a = 10 -- Questo è un commento alla riga di codice.

Per commentare una porzione di codice invece si utilizza questa sintassi:

--[[
a = 10
print (a)
]]--

Tutto il codice racchiuso tra i simboli di doppio trattino e doppia parentesi


quadra è sempre commentato.

La corretta scelta per il nome delle variabili

Quando crei una nuova variabile con LUA devi rispettare alcune regole e
convenzioni, il nome di una variabile deve sempre iniziare con una lettera
oppure con il simbolo underscore (_). Non sono ammessi spazi e potrai
utilizzare solo numeri, lettere e simboli underscore. Il linguaggio è
case_sensitive, questo significa che fa distinzione tra maiuscole e
minuscole: una variabile chiamata Var1 è diversa da un’altra chiamata var1.
Le seguenti parole sono riservate al linguaggio e non possono essere
utilizzate per creare il nome di una variabile:

and
break
do
else
elseif
end
false
for
function
if
in
local
nil
not
or
repeat
return
then
true
until
while

Tipi di dato primitivo in LUA

LUA è un linguaggio tipizzato dinamicamente, questo significa che durante


l’assegnamento di una variabile è definita anche la sua natura. I tipi
primitivi consentiti nel linguaggio sono i seguenti:
nil – Una variabile non inizializzata assume sempre il valore nil,
ovvero nessun tipo e valore associato a essa. Una variabile
correttamente inizializzata può essere resettata attraverso la parola
chiave nil.
Boolean – Il tipo booleano può assumere due distinti valori (vero[true]
oppure falso[false]). La condizione nil di una variabile è considerata
come false, mentre qualsiasi altro valore associato a una variabile è
considerato true.
Numbers – Rappresentazione logica dei numeri reali (con doppia
precisione).
String – Una stringa è una sequenza di caratteri. Caratteri a 8 bit e il
valore zero sono ammessi.
Tables – L’unica struttura dati nativa di LUA. Viene implementata
attraverso un vettore[array] associativo. Questo vettore è composto da
coppie chiave/valore. Le chiavi possono essere numeri, oppure valori
alfanumerici eccetto il valore nil.
Functions – Sono la vera essenza di LUA. Tipicamente le funzioni
sono indirizzabili da una variabile, possono essere utilizzate quindi
come argomento per altre funzioni e hanno la proprietà di restituire dei
risultati.

If Then Else

Il costrutto If testa la condizione (vero/falso) nel confronto di due variabili


oppure due espressioni.
Il costrutto nel suo prototipo prevede la seguente sintassi:

If <condizione> Then
<espressione vera>

Else

<espressione falsa>
End
La condizione Else (espressione falsa) è opzionale e può essere omessa.
Osserva alcuni esempi:

(1)
if k > 0 then k=k-1 end

Se k è maggiore di zero al valore di k sarà sottratta una unità.

(2)

If val1 == val2 then


Print(“i valori sono uguali”)

Else

Print(“i due valori sono diversi”)


End

In questo secondo esempio sono confrontate due variabili (val1 e val2). Se


dal confronto sono uguali (condizione vera) sarà notificato nella consolle
l’uguaglianza, altrimenti (condizione falsa) sarà notificato l’opposto.

(3)

If K > 10 then
If K ~= M then
Printf(“K è diverso da M”)
Else
Print(“K è uguale ad M”)
End
End
In questo terzo esempio puoi notare l’utilizzo innestato del costrutto If, con
questa tecnica puoi andare in profondità e generare condizioni di confronto
anche complesse. È buona norma in questi casi non esagerare mai con gli
innesti e scrivere un codice sempre indentato bene, l’ordine e la pulizia
nella scrittura del codice sono importantissimi! Come puoi notare il
costrutto if più esterno ha omesso la variante Else.
Il linguaggio LUA non ha nella sua sintassi un costrutto di tipologia switch
(interruttore multiplo). Questo scoglio è facilmente superabile
dall’estensione elseif del costrutto if.
Osserva il seguente esempio:

If opt == “+” then


ris = a + b

elseif opt == “-” then

ris = a - b

elseif opt == “*” then

ris = a – b

elseif opt == “/” then

ris = a – b

else

print(“operatore non valido”)


end

In questo caso la variabile opt (che simula un operatore matematico) viene


confrontata con il simbolo dei quattro operatori elementari (+,-,*,/). Di fatto
si ha un filtro con quattro possibili ramificazioni in corrispondenza
dell’operatore corretto, nel caso di mancata corrispondenza essa sarà
notificata dalla ramificazione else.
Espressioni

Un’espressione è tutto ciò che ha un valore: costanti numeriche, stringhe,


variabili, operatori binari e funzioni sono esempi di quanto appena detto.

Operatori Aritmetici

I simboli + , - , * , / , % , ^ sono chiamati operatori aritmetici. Essi possono


essere utilizzati indistintamente in un assegnamento di un’espressione,
osserva i seguenti esempi:

x = 3 * 5 + 4
print(x)

Stamperà il valore 19, questo perché la moltiplicazione (e anche la


divisione) hanno priorità su somma e sottrazione, per evitare questo è bene
utilizzare le parentesi tonde.

k = 24 % 7
print(k)

L’operatore modulo, molto utilizzato in diversi algoritmi, restituisce il resto


di una divisione, in questo caso a k viene assegnato il valore 3.

l = 2^3
print(l)

L’operatore potenza calcola, come intuirai, l’elevamento a potenza di un


numero. Nell’esempio appena visto viene assegnato alla variabile l il cubo
del numero 2 ovvero 8.

Operatori relazionali

Gli operatori relazionali sono utilizzati per confrontare variabili ed


espressioni, il risultato di tale confronto è un valore di tipo “vero” oppure
“falso”.
Di seguito una lista di tutti gli operatori:

< “minore”
> “maggiore”
<= “minore o uguale a”
>= “maggiore o uguale a”
== “uguale a”
~= “diverso da”

Numeri e stringhe sono confrontati per valore; tabelle e funzioni viceversa


vengono confrontati per riferimento e due elementi sono considerati uguali
se fanno riferimento allo stesso oggetto. Se un nuovo oggetto viene creato
esattamente uguale a un oggetto precedente sarà a ogni modo considerato
diverso.
Di seguito alcuni esempi di confronto con operatori relazionali:

local a = 10

local b = 20

print ( a < b) -- true

print ( a >= b ) -- false

print ( a == b ) -- false

print ( a ~=b) -- true


Operatori logici

Gli operatori logici nel linguaggio LUA sono di tre tipologie:

and
or
not

L’operatore and restituisce il primo argomento se esso è falso oppure nil


(puntatore non assegnato), altrimenti restituisce il secondo valore.
L’operatore or restituisce il primo argomento se è diverso da nil oppure
false, viceversa restituisce il secondo argomento.
L’operatore not restituisce il valore opposto di un argomento. False diventa
true e viceversa.
Un esempio esaustivo:

local a = 10

local b = 20

print( not( a <= b) or ( a == b)) -- false

print( ( a ~= b ) and ( a > b )) --true

Operatore concatenazione di stringhe

L’operatore concatenazione di stringhe nel linguaggio LUA e definito dal


simbolo (..), questo operatore non fa altro che unire due stringhe in una
unica ad esempio per un output sullo schermo. È possibile concatenare due
o più stringhe, oppure numeri e stringhe, in questo ultimo caso i numeri
saranno convertiti in stringhe.
Osserva il seguente esempio:
local str1= “Ciao”

local str2=”Mondo”

local num=2013

print(str1 .. str2 .. “anno:” .. num)

L’output di questo codice sarà la stringa: Ciao Mondo anno: 2013

Operatore lunghezza di una stringa

L’operatore lunghezza di una stringa (#) restituisce il valore numerico della


lunghezza di una stringa, ovvero il numero di caratteri che la definisce.
Ogni carattere ha la dimensione di un bye.
Il seguente esempio illustra il funzionamento dell’operatore:

local titolo=”Corso linguaggio LUA”

print(#”ciao”) -- restituisce il numero 4

print(#”\n”) -- restituisce il numero 1

local prova = (#"pippo")

print(prova) -- restituisce il numero 5

local prv2 = "ciao mondo"

local prv3 = (# prv2) -- restiuisce il numero 10

print(prv3)

Precedenza degli operatori con LUA


La precedenza degli operatori nel linguaggio LUA ha la seguente priorità
dalla più alta alla meno alta:

^
not # - (unario)
*/
+-
..
< > <= >= ~= ==
and
or

Tutti gli operatori hanno associatività a sinistra tranne i casi particolari degli
operatori ^ e .. che hanno associatività a destra. Puoi a ogni modo utilizzare
le parentesi tonde per cambiare la precedenza degli operatori secondo le tue
necessità. Quando due operatori devono essere risolti e hanno la stessa
precedenza, sarà valutato prima quello a sinistra, osserva il seguente
esempio:
print( 9 – 3 + 7) – questa operazione restituisce il numero 13

Le stringhe nel linguaggio LUA

Una stringa non è altro che una sequenza finita di caratteri, puoi
memorizzare sia caratteri numerici che alfanumerici ma anche veri e propri
dati binari mascherati al suo interno.
Fondamentalmente esistono tre tipologie di quotazione per le stringhe: la
prima con il doppio apice, la seconda con il singolo apice e la terza con
doppia parentesi quadrata.
Osserva i seguenti esempi:

local str1 = “questa è una stringa”


local str2 = ‘anche questa è una “stringa” ma con quotazione’

local str3 = [[questa invece è una “stringa” oppure ‘string’


con doppia quotazione]]

Come puoi notare nell’esempio appena citato esistono fondamentalmente


tre quotazioni per le stringhe, la seconda e la terza hanno lo scopo di
quotare rispettivamente i caratteri speciali doppio apice e doppio apice con
singolo apice.
Nel linguaggio LUA le stringhe sono dati immutabili, questo significa che
la loro lunghezza non è variabile a run time (tempo di esecuzione). È
possibile però creare una nuova stringa sostituendo occorrenze da una
stringa precedente.
Osserva il seguente esempio:

local a = “sono la prima stringa”

local b = string.gsub(a, “prima”, “seconda”)

print(b) -- stampa a video la stringa: sono la seconda stringa.

Le stringhe nel linguaggio LUA possono avere qualsiasi dimensione, non


esiste un limite e tecnicamente potresti memorizzare all’interno di esse
anche un intero libro, compresa la sua formattazione. Per ricavare la
lunghezza di una stringa puoi utilizzare l’operatore (#) come nel seguente
esempio:

local str1 = “ciao mondo”

print(#str1) -- stampa a video il numero 10

local b = (# str1) -- memorizza nella variabile b il valore 10

Le sequenze di uscita nel linguaggio LUA per le stringhe sono molto simili
a quelle del linguaggio C, eccoti un elenco esaustivo con la descrizione:
\a segnale acustico
\b back space
\f form feed
\n new line
\r carriage return
\t horizontal tab
\v vertical tab
\\ backslash
\” doppio apice
\’ singolo apice

Tutti questi codici di uscita possono essere utilizzati indistintamente per


quotare il testo della stringa.
LUA prevede un meccanismo automatico di conversione tra stringhe,
numeri e viceversa. Ogni operatore numerico applicato a una stringa
produce un valore numerico, se questa operazione è consentita. Alcuni
esempi:

print(“13” + 4) -- stampa il numero 17

print(1 + “ciao”) -- errore, la stringa non è la


rappresentazione di un numero.

local line = “10”

local val = tonumber(line) -- converte la stringa “10” nel


numero 10

local val1 = 10

local str1 = tostring(val1) -- converte il valore 10 nella


stringa “10”

Cicli

Nella programmazione e di conseguenza nel linguaggio LUA i cicli


rivestono un ruolo di fondamentale importanza. La loro funzione è
principalmente quella di eseguire processi e operazioni all’uomo
normalmente noiosi e ripetitivi. Questi costrutti si dividono
fondamentalmente in due grandi categorie:

Cicli definiti
Cicli indefiniti

Un ciclo è detto definito quando si è a conoscenza del numero di iterazioni


(ripetizione del codice). Ad esempio, in un simulatore di corse sportive il
numero di giri in un gran premio è sintetizzabile con un ciclo definito,
conoscendone esattamente il valore.
Un ciclo, viceversa, è detto indefinito quando non si conosce a priori il
numero di iterazioni, esso può dipendere da un valore causale o
indipendente dal programma stesso. Si pensi ad esempio a una sonda che
ogni quanto di tempo debba campionare una temperatura e attivare un
sistema di refrigerazione al verificarsi di un certo valore, non sappiamo
quanti cicli di lettura ci saranno prima dell’attuazione, essa dipende da
condizioni ambientali esterne al programma di controllo.
Questa seconda tipologia di cicli, detti indefiniti, si divide a sua volta in due
categorie:

Ciclo post fisso


Ciclo pre fisso

Nel ciclo post fisso la condizione di terminazione del ciclo è valutata in


fondo ad essa, questo significa che almeno una volta il ciclo sarà sempre
eseguito.
Nel ciclo pre fisso la condizione è valutata a monte e quindi potrebbe
accadere che una determinata condizione non soddisfatta faccia saltare
completamente il ciclo.

Il ciclo for
Il ciclo for appartiene alla categoria dei cicli definiti. Nel linguaggio LUA
presenta la seguente sintassi:

for var = exp1, exp2, exp3 do

.. codice all’interno del ciclo ...

end

exp1 è il valore di partenza, exp2 il valore di terminazione, exp3


(opzionale) definisce il passo computazionale del ciclo e se viene omesso
sarà di default impostato a 1.

Ciclo for generico


Per scandire gli elementi di una tabella e stampare ad esempio le coppie
chiave valore è possibile utilizzare un ciclo for generico. Nella sua forma
standard il ciclo ha la seguente sintassi:

for k , v in pairs( t ) do

.. codice ..

end

t è una generica tabella generata in LUA


k è la chiave della tabella
v è il valore della tabella

All’interno del ciclo è possibile eseguire qualsiasi operazione si desideri


con le coppie chiave valore recuperate a ogni iterazione. Osserva il
seguente esempio:
table = {“lunedì”,
“martedì”,”mercoledì”,”giovedì”,”venerdì”,”sabato”,”domenica”}
for k, v in pairs(teble) do print(k ..” “..v \n) end

L’output del seguente codice sarà:

1 lunedì

2 martedì

ecc …

Questo codice, come puoi vedere, stamperà nella consolle un elenco


numerico con i giorni della settimana, esattamente come è definito nella
coppia chiave valore della tabella definita precedentemente.

Il ciclo while
Il while è il primo dei due cicli afferenti la categoria di ciclo indefinito
introdotta precedentemente, la sua specializzazione è definita come
prefissa. Questo significa che la condizione di verità è testata a monte del
ciclo. Ecco la generica sintassi di un ciclo while in LUA:

while (condizione) do
.. istruzioni di loop ..
end

Fino a che la condizione risulta essere vera il ciclo esegue le sue iterazioni,
ovvero la ripetizione del codice all’interno delle parole chiavi do e end.
Appena la condizione è falsa il ciclo smette di iterare e il programma
continua con il normale flusso. Osserva ora il seguente esempio pratico:
local i = 1

while ( i <= 10 ) do

print(i)

i=i+1

end

La condizione testata all’inizio del ciclo è vera fino a che la variabile i non
è maggiore di 10, quindi il ciclo eseguirà esattamente dieci iterazioni. In
questo caso il ciclo “emula” un for ma non è difficile intuire che attraverso
delle condizioni aleatorie all’interno del ciclo è possibile modificare la
condizione in un qualsiasi momento.

Il ciclo repeat
Il repeat è il secondo dei due cicli afferenti la categoria di ciclo indefinito
introdotta precedentemente, la sua specializzazione è definita come
postfissa. Questo significa che la condizione di verità è testata a valle del
ciclo. Ecco la generica sintassi di un ciclo repeat in LUA:

repeat
.. istruzioni di loop ..
until (condizione)

Come puoi ben notare la condizione è testata alla fine del ciclo, questo
implica che almeno una volta il ciclo sarà sempre eseguito. Osserva il
seguente esempio:

repeat
line = io.read( )
print(line)
until line ~= “ “

Il seguente codice stampa nella consolle le prime n linee di un input, al


raggiungimento di una linea vuota ha termine la stampa.
Diversamente da altri linguaggi il ciclo repeat di LUA ha una ulteriore
caratteristica, è possibile testare nella condizione il valore di una variabile
creata all’interno del ciclo stesso, ad esempio il risultato di un’elaborazione.
Osserva il seguente esempio generico:

repeat
.. istruzioni de loop ..
local var
until (var)

La variabile var creata internamente al ciclo è utilizzata anche nella


condizione del ciclo repeat.

Tabelle

Le tabelle sono l’unico dato proprietario e strutturato del linguaggio LUA.


Attraverso esse è possibile rappresentare array, liste, sets, grafi, matrici e
molto altro. Puoi pensare a una tabella di LUA come a un array associativo,
un array associativo può essere indirizzato oltre che da numeri anche con
valori alfanumerici. Tutte le strutture appena elencate sono rappresentate
efficientemente attraverso le tabelle, in questo paragrafo avrai modo di
studiare con esempi pratici l’implementazione di alcuni tipi di dati
rappresentabili con le tabelle di LUA.
Osserva il seguente esempio di un array mediante tabella:

colori = {
[1] = “Verde”
[2] = “Blu”
[3] = “Giallo”
[4] = “Arancione”
[5] = “Rosso”
}

print(colori[2]) -- stampa nella consolle il colore Blu

Un array di mille elementi tutti inizializzati al valore zero potrebbe essere


inizializzato nel seguente modo:

vett = { }

for i = 1, 1000 do

vett[i] = 0
end

Come puoi vedere la forma è molto compatta e pratica, ogni singolo


elemento viene inizializzato con un valore posto a 0. È possibile creare
anche array con indici diversificati, basta cambiare l’intervallo nel ciclo for.
Una modalità compatta per la creazione di un vettore potrebbe essere la
seguente:
cubi = {1, 8, 27, 64, 125, 216, 343}

È generato un vettore di 7 elementi con il cubo dei primi 7 numeri naturali.


Osserva ora una possibile sintassi per realizzare una matrice (array a due
dimensioni):

matrice = { }
for i = 1, N do
matrice[ i ] = { } -- Creazione di una riga
for j = 1, M do
matrice[ i ],[ j ] = 0 -- Inizializzazione a zero della
singola cella
end
end

Questo codice genera una matrice N x M interamente inizializzata al valore


0.
Attraverso le tabelle è possibile implementare qualsiasi struttura di dati, in
modo semplice ed efficiente. Osserva ad esempio come viene creata una
lista:

list = nil

list1 = nil

listn = nil

list = { next = list1, value = v }

list1 = { next = list2, value = v1 }

listn = { next = listn+1, value = vn}

In questo modo è possibile creare un numero arbitrario di nodi della lista,


ad ogni nodo è possibile associare anche più di un valore, e tecnicamente è
possibile realizzare anche una lista bidirezionale implementando il
puntatore al nodo precedente. Ogni nodo in definitiva è rappresentato da
una lista.
Per scorrere i nodi è sufficiente un semplice ciclo while, impostato nel
seguente modo:

local l = list
while l do
.. lettura dei valori della lista (es:l.value) ..
l = l.next
end
Come puoi notare nel codice è all’interno del ciclo stesso che viene caricato
il prossimo nodo della lista; quando sarà raggiunto l’ultimo nodo valido,
non essendoci un nodo successivo, la condizione del ciclo while sarà
impostata a falsa e di conseguenza si avrà l’uscita dal ciclo.
3. Metatabelle e metametodi

Nel linguaggio LUA sono consentite di default diverse operazioni: è


possibile ad esempio confrontare numeri, concatenare stringhe, inserire
coppie valori/chiave all’interno di una tabella e molto altro. Ad ogni modo
non è possibile effettuare operazioni più complesso come confrontare
tabelle, funzioni ecc.. Se non si utilizza uno strumento adeguato: le
metatabelle.
Le metatabelle in sostanza permettono di cambiare il comportamento di un
valore quando è confrontato con un operatore non definito di fabbrica
dall’interprete LUA. Per esempio è possibile utilizzare metatabelle per
definire il comportamento di due tabelle (es: tabella a e tabella b) quando
tra essere viene definito un nuovo operatore (+). In sostanza quando si
effettua l’operazione a+b LUA verifica che sia presente una metatabella per
l’operatore (+) e se essa ha attivo il campo __add. Se questo campo è
presente viene avviato il metametodo, una funzione appositamente
realizzata per il comportamento dell’operatore addizione tra le due tabelle.
In generale è possibile definire per una tabella svariati metametodi, è
possibile ridefinire ad esempio gli operatori matematici, quelli logici oppure
creare metametodi personalizzati.

Esempio operativo

Osserva ora il seguente esempio operativo che illustra il comportamento di


una metatabella associata a una tabella e per essa definisce diversi
metametodi:

1. Crea due tabelle vuote, la prima “mt” è la metatabella. La seconda


tabella “Set” rappresenterà l’oggetto set.

local mt = {}

Set = {}

2. Un set non è altro che un vettore di elementi non omogenei e non


necessariamente ordinati. Il costruttore sottostante genera questa
struttura passando alla tabella gli elementi che verranno generati
dinamicamente attraverso coppie chiave valore all’interno del ciclo for.
Inoltre alla tabella viene associata una metatabella attraverso la
funzione “setmetatable”.

function Set.new (vals)


local set = {}
setmetatable(set, mt) -- esempio di metatabella
for _, v in ipairs(vals) do set[v] = true
end
return set
end

Attraverso il costruttore è possibile generare oggetti sets, ma per rendere


questi oggetti più interessanti è opportuno ridefinire gli operatori.
Nell’esempio che segue imparerai a creare una funzione di unione, ovvero
una funzione che unisce due oggetti set senza generare duplicati. Tale
funzione deve poi successivamente essere legata a un metametodo. La
tabella locale res, restituita dalla funzione, viene popolata con gli elementi
di due tabelle set.
-- funzione union di un set

function Set.union (a, b)


local res = Set.new {}
for k in pairs(a) do res[k] = true end
for k in pairs(b) do res[k] = true end
return res
end

1. Ottimo lavoro, per rendere più operativo il tuo oggetto set appena
creato dovrai ora realizzare due funzione per poter serializzare gli
elementi della tabella set e stamparli ad esempio nella consolle. La
prima funzione “Set.tostring” restituisce una concatenazione ben
formata di tipo stringa, leggendo gli elementi del set attraverso un
ciclo for.
-- funzione di concatenazione
function Set.tostring (set)
local l = {}
for e in pairs (set) do
l[#l +1 ] = e
end

return "{" .. table.concat(l, ", ") .. "}"

end

2. La funzione “Set.print” semplicemente stampa la stringa generata dalla


funzione precedente.
-- funzione di stampa di un set

function Set.print (s)


print(Set.tostring(s))
end

Il lavoro preparatorio è stato realizzato, ora è necessario legare il


metametodo alla metatabella ridefinendo l’operatore addizione e poi sarà
possibile effettuare test tecnici per controllare l’effettiva funzionalità del
codice.

1. La ridefinizione dell’operatore addizione avviene mediante la chiamata


metatabella.__add = metametodo. La generazione di un oggetto set
invece viene realizzata invocando il costruttore e passando ad esso gli
opportuni valori desiderati.
mt.__add = Set.union -- inizializzazione del metametodo
addizione nella metatabella

-- esempio di funzionamento

s1 = Set.new {10, 20, 30, 40, 50}


s2 = Set.new {5, 20, 30, 2}

2. Puoi testare la natura comune della metatabella per i due oggetti


appena creati, utilizzando la funzione “getmetatable” passata come
parametro alla funzione print. Inoltre una volta ridefinito l’operatore
addizione diventa del tutto lecito effettuare un’operazione come la
somma tra due set, potente vero?

print(getmetatable(s1))
print(getmetatable(s2))

s3 = s1 + s2

3. Esegui ora un test finale stampando i due oggetti set prima dell’unione
e l’oggetto set risultante dalla somma dei precedenti.

Set.print (s1)
Set.print (s2)

print("Unione:")

Set.print (s3)

Prova per esercizio a sviluppare la ridefinizione dell’operatore di


moltiplicazione (intersezione) tra oggetti set. Un piccolo aiuto: “mt.__mul
= Set.intersection”

Soluzione esercizio

Il primo passo consiste nella realizzazione di un nuovo metametodo che


servirà per la ridefinizione dell’operatore moltiplicazione; questa funzione
esegue l’intersezione tra set (insiemi). L’operazione consiste nel mantenere
nel nuovo oggetto solo gli elementi comuni ai due oggetti moltiplicati
precedentemente.
-- funzione intersezione di un set

function Set.intersection (a, b)


local res = Set.new{}
for k in pairs(a) do
res[k] = b[k]
end
return res
end

A questo punto non ti resta che aggiungere la redifinizione dell’operatore


alla metatabella mediante il metametodo, sei infine pronto al test di prova.

1. Ridefinizione dell’operatore moltiplicazione mediante la metatabella e


il metametodo.
mt.__mul = Set.intersection -- inizializzazione del
metametodo moltiplicazione nella metatabella

2. Test del corretto funzionamento del nuovo operatore ridefinito.

s4 = s1 * s2

print("Intersezione:")

Set.print (s4)

Oltre agli operatori di moltiplicazione è possibile ridefinire anche i seguenti


operatori: __sub (sottrazione), __div (divisione), __unm (negation),
__mod(modulo), __pow(esponenziale),__concat(concatenazione).
Metametodi relazionali

Un’altra famiglia molto importante di operatori è rappresentata da quelli


relazionali. Il linguaggio LUA ti permette di poterli ridefinire per i tuoi
oggetti con la medesima tecnica vista precedentemente con gli operatori
aritmetici. In questo caso però LUA ottimizza tutto il flusso e prevede la
definizione di soli tre operatori: __eq (uguale a), __lt (minore di) e
__le(minore o uguale a ). Da essi, poi, con semplici scambi di operandi è
possibile derivare tutti gli altri (diverso, maggiore e maggiore uguale).
Osserva il seguente esempio, intervenendo sull’oggetto set visto
precedentemente.
Diversamente dagli esempi visti in precedenza, imparerai ora a creare dei
metametodi inline. Ovvero la funzione viene generata durante
l’assegnamento della ridefinizione di operatore alla metatabella. Questa
funzione restituisce false se nel set del parametro b non vi sono tutti gli
elementi presenti in a (condizione minore o uguale).
-- funzione definizione operatore minore uguale

mt.__le = function (a, b)


for k in pairs(a) do
if not b[k] then
return false
end
return true
end
end

L’operatore minore viene ridefinito sfruttando l’operatore minore uguale


precedentemente creato e sfruttando la proprietà logica che un oggetto set è
minore di un altro se è minore uguale e non viceversa.

-- funzione definizione operatore minore


mt.__lt = function (a, b)

return a <= b and not(b <= a)


end

Anche l’operatore di uguaglianza ridefinito sfrutta l’operatore minore


uguale ricorrendo alla proprietà logica che un oggetto set è uguale a un altro
quando entrambi sono minori uguali scambiando gli operatori.

-- funzione definizione operatore uguaglianza

mt.__eq = function (a, b)

return a <= b and b <= a


end

Genera ora due nuovi oggetti set e per essi prova diverse configurazioni,
utilizzando gli operatori ridefiniti precedentemente e passati come
parametro alla funzione print.

sm1 = Set.new {3, 7}


sm2 = Set.new {3, 9, 7}

print(sm1 <= sm2)

print(sm1 < sm1)

print(sm1 > sm2)

print(sm1 == sm2 * sm1)

print(sm1 ~= sm2)

La funzione print permetterà l’output nella consolle del compilatore, per


verificare immediatamente la correttezza delle operazioni eseguite.
Metametodi di accesso alle tabelle

Per capire come funzionano i metametodi di accesso alle tabelle e la loro


implementazione osserva il seguente esempio:

1. Crea una tabella con quattro attributi: le coordinate e le dimensioni di


una figura piana rettangolare e la rispettiva metatabella.
-- creazione di un prototipo con valori di default

prototipo = {x = 0, y = 0, larghezza = 100, altezza = 100}

mt = { } -- creazione della metatabella

2. Realizza ora un costruttore settando la metatabella alla tabella appena


creata.
-- creazione della funzione costruttore

function new (par)


setmetatable(par, mt)
return par
end

3. Adesso dovrai creare un metametodo __index che in sostanza permette


il completamento della creazione di un oggetto, se al costruttore
vengono passati un numero parziale di parametri. I valori di default
sono quelli inseriti all’atto della creazione della tabella prototipo.
Osserva le due forme, quella normale e quella compatta.
-- definizione del metametodo __index

-- mt.__index = function (_, chiave)

-- return prototipo[chiave]
-- end

mt.__index = prototipo -- forma compatta

4. Crea ora un nuovo oggetto partendo dal prototipo, soltanto con i primi
due parametri.
-- Creazione di un nuovo oggetto prototipo con i primi due
parametri

w = new {x = 10, y = 20}

5. Se tutto è andato a buon fine gli attributi di altezza e larghezza saranno


settati con i valori di default. Per verificare che questo sia avvenuto,
stampa i valori nella consolle.
-- test corretto funzionamento metametodo __index

print(w.larghezza)
print(w.altezza)

Tabelle con valori di default

In alcuni casi può essere utile generare valori di default per una tabella
attraverso una funzione associata, sfruttando sempre le metatabelle.

1. Crea una funzione “setDefault” che ti permette di creare un valore di


default per una tabella attraverso una metatabella.
-- Funzione per settare valori defaults a runtime

function setDefault (t, d)


local mt = { __index = function () return d end }
setmetatable (t, mt)
end

2. Testa ora il codice appena realizzato, creando una tabella con due
valori, stampandone solo uno e cercando di visualizzare un terzo
valore non presente che restituirà nil. Per ovviare a questo, invoca la
funzione alla tabella appena creata e riprova la stampa, noterai a
questo punto che il secondo valore viene sostituito dal valore di default
così come ti saresti aspettato.
-- Test della funzione

tabella = { a=99, b=33 }

print(tabella.a, tabella.c)

setDefault(tabella, 77)

print(tabella.a, tabella.c, tabella.z)

Se desideri applicare valori di default a più tabelle puoi ottimizzare il


codice, in modo da avere una sola metatabella comune a ogni invocazione.

1. Attraverso l’operatore ( ___ ) avrai la possibilità di istanziare una sola


tabella mt e di salvare dentro di essa tutti i valori di default che andrai
a creare per più tabelle, ottimizzando quindi il processo di creazione.
Osserva il seguente esempio, per capire come modificare la funzione
precedentemente realizzata.
-- Funzione per settare valori default a runtime
ottimizzata

local mt = { __index = function (t) return t.___


end }
function setDefaultOpt (t, d)
t.___ = d
setmetatable (t, mt)
end
2. Ripeti il test eseguito precedentemente.
-- Test della funzione ottimizzata

tabella = { a=99, b=33 }


print(tabella.a, tabella.c)
setDefaultOpt(tabella, 77)
print(tabella.a, tabella.c, tabella.z)

Tracciare l’accesso alle tabelle

Lavorando con le tabelle per taluni casi può essere necessario tracciarne
l’accesso, in LUA questa operazione è possibile attraverso le metatabelle e
gli strumenti messi a disposizione da esse:

1. Crea una tabella generica vuota, assegnale un puntatore (puntatore alla


tabella originale) e quindi genera una seconda tabella identica alla
prima (tabella proxy).
-- Tracciare l'accesso ad una tabella

t = {}

local _t = t -- accesso privato alla tabella originaria

t = {} -- creazione del proxy

2. Inizia ora la realizzazione della metatabella, andando a programmare il


primo campo esclusivo __index che intercetta un accesso unico alla
tabella.

-- creazione della metatabella


local mt = {
__index = function (t, k)
print("Accesso all'elemento: " .. tostring(k))
return _t[k] -- accesso alla tabella di
origine
end,
3. Crea ora un secondo campo esclusivo __newindex per la notifica dopo
l’aggiornamento di un valore all’interno di una tabella, anche in questo
caso sfrutterai l’accesso provato ed il proxy per interfacciarti.

__newindex = function (t, k, v)


print("Aggiornamento dell'elemento: " ..
tostring(k) .. " all'elemento: " .. tostring(v))
_t[k] = v
end
}

Prima di eseguire un nuovo test, setta la metatabella:

setmetatable(t, mt)

-- Test accesso ad una tabella

t[3] = "ciao mondo"


print(t[3])

Anche per la tracciabilità dell’accesso, così come per i valori di default è


possibile ottimizzare il codice e permettere l’operazione su un numero
arbitrario di tabelle senza ulteriori sgravi alle risorse di sistema.

1. Crea una tabella “index” che utilizzerai per memorizzare tutti gli indici
degli accessi alle tabelle, a questo punto il codice è simile al
precedente e prevede l’implementazione dei due campi esclusivi
__index e __newindex con l’opportuna implementazione della tabella
precedentemente realizzata.

local index = {}

local mt = {
__index = function (t, k)
print("Accesso all'elemento: " .. tostring(k))
return t[index][k] -- accesso alla tabella di
origine
end,

2. Il secondo campo esclusivo:

__newindex = function (t, k, v)

print("Aggiornamento dell'elemento: " .. tostring(k) .. "


all'elemento: " .. tostring(v))

t[index][k] = v
end,

3. Per governare la meccanica avrai bisogno di un terzo campo esclusivo


__pairs così realizzato:

__pairs = function (t)

return function (t, k)


return next(t[index], k)
end, t
end
}
4. L’implementazione del proxy all’interno della metatabella è realizzato
infine dalla funzione “track” che di fatto rende funzionale tutto
l’impianto:

function track (t)


local proxy = {}
proxy[index] = t
setmetatable(proxy, mt)
return proxy
end

Per testare quanto appena realizzato crea una nuova tabella “t1”. Ti basterà
a questo punto assegnare alla tabella la funzione “track” appena realizzata,
che ha come parametro la tabella stessa, e l’impianto è attivo.
-- Test accesso a più tabelle

t1 = { }
t1=track(t1)

t1[3] = "accesso a più tabelle"


print(t1[3])

Tabelle di sola lettura

Attraverso le metatabelle e i metametodi è possibile realizzare tabelle di


sola lettura, in questo ultimo paragrafo realizzerai un esempio in tal senso.

1. Realizza una funzione “soloLettura” che genera un proxy e una


metatabella con i campi esclusivi __index e __newindex, quest’ultimo
avrà il compito di generare l’eccezione “error” quando si tenterà di
modificare un campo di una tabella di sola lettura.
-- Tabelle di sola lettura

function soloLettura (t)


local proxy = {}
local mt = {
__index = t,
__newindex = function (t, k, v)
error("stai cercando di accedere ad una
tabella di sola lettura", 2)
end
}
2. Setta ora la metatabella e restituisci il proxy dalla funzione.

setmetatable(proxy, mt)

return proxy

end

Per il test non ti resta che realizzare una nuova tabella, sfruttando la
funzione “soloLettura” come nell’esempio sottostante:
-- Test tabella solo lettura

colori =
soloLettura{"Giallo","Verde","Blu","Rosso","Arancione"}

print(colori[5])

colori[2] = "Viola"
4. L’ambiente
Introduzione

LUA memorizza tutte le variabili globali in un set di tabelle, definite come


“ambiente globale”. Questa tecnica permette di gestire con molta semplicità
le variabili globali e di poter accedere a loro come si accederebbe a una
semplice tabella. Per eseguire questa e altre operazioni si utilizza
l’operatore _G.
Osserva il seguente esempio:
-- Stampa di tutte le variabili globali

for n in pairs(_G) do print(n) end

Questo ciclo in sostanza stampa nella consolle tutte le variabili globali


dell’ambiente utilizzando l’operatore _G che punta a esse.
Quando dichiari una variabile in LUA hai due possibilità: far precedere al
nome della variabile il prefisso “local” oppure dichiarare direttamente la
variabile. In questo secondo caso stai a tutti gli effetti creando una variabile
globale, ovvero una variabile visibile da tutto l’ambiente LUA, file main.lua
ed eventuali moduli correlati. Ti consiglio, per chiarezza nel codice, di
utilizzare l’operatore _G quando dichiarerai una variabile che intendi
utilizzare in tutti i tuoi moduli, come ad esempio un setting del volume
audio etc.
Avvia ora il simulatore, noterai l’elenco di tutte le variabili globali del
sistema inizializzate nell’ “ambiente globale”.
Riassumendo, dichiarare una variabile globale in questi due differenti modi
sortisce lo stesso effetto:

Audio = 10
_G.Audio = 10
È bene, per pulizia e leggibilità del codice, utilizzare la seconda
nomenclatura nel caso si voglia predisporre una variabile globale a uso
generale in tutti i moduli del progetto. La lettura di tale variabile non
prevede a ogni modo la presenza del prefisso, mentre per ogni modifica è
necessario inserirlo.
Per avere un sunto più completo di tutte le variabili globali è possibile
stampare anche l’indirizzo di memoria di ognuna, attraverso la seguente
sintassi:

for k,v in pairs( _G ) do


print( k .. " => ", v )
end

Variabili dinamiche con nomi globali

In Lua non è possibile definire dei domini di varabili attraverso i costrutti


offerti dal linguaggio, tuttavia generare delle regole per essi è molto
semplice. Attraverso due funzioni (getfield e setfield) è possibile definire
delle regole per memorizzare particolari valori a indirizzi prefissati.
La prima funzione “getfield” permette di recuperare un valore memorizzato
in una gerarchia: (nome1.nome2.nomeN = val) e di restituire il valore per
l’assegnamento ad esempio a una variabile.
-- Accesso alle variabili globali con nomi dinamici

function getfield (f)


local v = _G -- puntatore alla tabelle delle variabili
globali
for w in string.gfind(f, "[%w_]+") do
v = v[w]
end
return v
end

La funzione “setfield” ha invece il compito di creare un dominio univoco


per la memorizzazione di una variabile nella sintassi:
(nome1.nome2.nomeN = val) attraverso un ciclo che riconosce il .(dot)
come marcatore. Osserva l’esempio seguente per carpirne il funzionamento:

function setfield (f, v)


local t = _G -- start with the table of globals
for w, d in string.gfind(f, "([%w_]+)(.?)") do
if d == "." then -- not last field?
t[w] = t[w] or {} -- create table if absent
t = t[w] -- get the table
else -- last field
t[w] = v -- do the assignment
end
end
end

Per comprendere bene il funzionamento di queste due funzioni prova il


seguente codice. La prima funzione definisce lo spazio dei nomi e il valore
da memorizzare. Attraverso la seconda è possibile ricavare il valore dallo
spazio di nomi passato come parametro.
-- Esempio operativo variabili globali con nomi dinamici

setfield("italia.bologna.montesanpietro", 40050)

cap = getfield("italia.bologna.montesanpietro")

print(cap)

Per un maggiore controllo sulle variabili in Lua è possibile definire delle


politiche di assegnamento e lettura, sollevando delle eccezioni nel caso non
venissero rispettate. Osserva il seguente codice che, attraverso un
“setmetatable” visto nel precedente volume, permette di generare due
tipologie di errore nel caso si tenti di assegnare un valore a una variabile
non dichiarata. Oppure l’operazione speculare di lettura.
-- Regolare l'accesso alle variabili globali

setmetatable(_G, {
__newindex = function (_, n)
error("stai cercando di assegnare un valore ad una
variabile non dichiarata "..n, 2)
end,
__index = function (_, n)
error("stai cercando di leggere una variabile non
dichiarata "..n, 2)
end,
})

Per verifica crea una variabile globale assegnandole il valore 10 e attraverso


la funzione “rawset” disabilitala dal contesto globale. Al successivo
assegnamento provocherai così un’eccezione.
-- Esempio di accesso regolarizzato

var1 = 10

rawset(_G,"var1",false)

var1 = 10

Tecnica per memorizzare variabili globali

Per gestire al meglio le variabili globali e non incappare in errori di


duplicazione è possibile creare una tabella in un modulo esterno che a tutti
gli effetti funzionerà come quella di default indicizzata dal prefisso “_G”.
Crea un nuovo file nominandolo globals.lua e al suo interno inserisci la
dichiarazione di una tabella e la restituzione della medesima.
-- variabili globali

local M = {}
return M

Nel file main.lua (ma questo può avvenire in qualsiasi file o modulo di
progetto) carica il file globals.lua attraverso la funzione “require” e una
variabile puntatore assegnata a essa. Ora potrai memorizzare le variabili
globali precedute dal puntatore e richiamarle da qualsiasi altro modulo con
la stessa tecnica:
local Glob = require( "globals" )

Glob.var1 = 20

print(Glob.var1)

Moduli e pacchetti

Nello sviluppo delle applicazioni è molto utile dividere la complessità del


codice in tanti file distinti più semplici e di più facile lettura. I moduli
hanno esattamente questo compito, incapsulare il codice e renderlo
disponibile ad altri segmenti di programma oppure ad altri programmi.
Nella programmazione funzionale un modulo non è altro che una collezione
di funzioni che svolgono determinati compiti, solitamente affini tra di loro.
Questi vengono più propriamente chiamati librerie: Corona SDK ne mette a
disposizione svariare e alcune le hai già utilizzate nei precedenti esempi di
questa collana. Le librerie di Corona SDK sono già integrate nel sistema e
non necessitano di essere incluse nei file attraverso la direttiva “require”,
operazione necessaria invece per i tuoi moduli personali. In questo
paragrafo imparerai gli elementi fondamentali per la costruzione di un
modulo e il suo corretto utilizzo. Una volta appresa questa tecnica potrai
realizzare tutte le librerie che desideri.

1. Crea un nuovo file con il tuo editor predefinito e nominalo modulo.lua.


Ogni modulo inizia con la creazione di una tabella e termina con la
parola chiave “return” seguita dal nome della tabella. La tabella ha lo
stesso nome del file.
local modulo = {}

return modulo

2. Per creare una funzione locale interna al modulo, utilizzabile solo da


altre funzioni del medesimo è necessario far precedere la parola chiave
“local” al nome della funzione:

local function private()


print("funzione privata del modulo")
end

3. Per creare una funzione di modulo utilizzabile esternamente a esso, è


invece necessario far precedere al nome della funzione il nome del
modulo seguito da un punto:

function modulo.foo()
print("Ciao Corona SDK")
end

4. In una funzione di modulo esportabile all’esterno è possibile


richiamare un’altra funzione di modulo attraverso la sintassi
“nome_modulo.nome:funzione”, oppure invocare una funzione
provata semplicemente con il nome della medesima, come
nell’esempio di seguito:
function modulo.bar()
private()
modulo.foo() -- necessita del prefisso per essere
invocata
end

5. È altresì possibile realizzare funzioni esterne con passaggio dei


parametri nella modalità conosciuta:

function modulo.mediabinaria(val1,val2)
return (val1+val2)/2
end

Per utilizzare il modulo all’interno di un altro modulo, oppure nel tuo main,
devi prima di tutto importarlo. Questa operazione avviene attraverso la
direttiva “require” con la seguente sintassi: var_puntatore =
require”nome_modulo”. A questo punto puoi utilizzare le funzioni del
modulo utilizzando la variabile puntatore che lo ha richiesto nella sintassi:
var_puntatore.funzione_del_modulo. Nell’esempio sottostante vengono
invocate le funzioni create nel precedente modulo:

m = require "modulo"

m.foo()

m.bar()

local ris = m.mediabinaria(4,16)


print(ris)
5. Programmare a oggetti con LUA
Introduzione

È finalmente arrivato il momento di affrontare la programmazione a oggetti


attraverso il linguaggio LUA. Il linguaggio non ha una sintassi predefinita
per la OOP ma è molto semplice e versatile costruire classi, meccanismo
dell’ereditarietà e polimorfismo attraverso tabelle, metatabelle e
metametodi. Tutti argomenti già trattati nei precedenti capitoli ma che ti
invito a ripassare. Esistono tuttavia molti approcci diversi in LUA per
l’implementazione della OOP, studierai quelli più diffusi e comunemente
utilizzati.

Nota: alcuni esempi in questo capitolo fanno riferimento a un


framework di sviluppo mobile che utilizza LUA come linguaggio di
scripting: Corona SDK. Il codice che utilizza esplicitamente questo
ambiente è indicato con il colore rosso. Se desideri approfondire il
framework puoi trovare una valida risorsa a questo indirizzo:
http://www.area51editore.com/corso-di-corona-sdk

Primo approccio

Inizia subito a studiare le classi partendo da un semplice esempio


implementativo, sfruttando tabelle e metatabelle. Crea un nuovo progetto
base, quindi duplica il file main.lua rinominandolo miaClasse.lua.

1. Una classe è a tutti gli effetti una tabella, quindi la prima operazione
consiste nella creazione di una tabella vuota con il nome della classe.
Per poter invocare i metodi di classe e quindi coppie chiavi valori della
tabella dovrai creare una sorta di “cortocircuito” nella tua tabella
attraverso l’operatore __index. Ecco il codice necessario:
local miaClasse = {} -- la tabella rappresenta la classe,
le istanze vengono generate attraverso metatable

miaClasse.__index = miaClasse -- tecnica per invocare i


metodi di classe

2. Il costruttore della classe (ovvero il metodo che di fatto istanzia un


nuovo oggetto e lo inizializza) utilizzerà un metodo chiamato news
seguito dai parametri necessari. Realizza ora un puntatore self alla
funzione setmetable che prenderà come parametri una tabella vuota e
la tabella della classe realizzata in precedenza. L’ultima operazione che
dovrai eseguire è la creazione di un attributo di classe inizializzato con
il parametro d’istanza e per finire il ritorno del puntatore self alla
funzione.
-- primo esempio di metodo costruttore

function miaClasse.new(init)

local self = setmetatable({}, miaClasse)


self.valore = init
return self

end

3. Non ti rimane che implementare alcuni metodi di classe, come ad


esempio i metodi getter e setter, per leggere e modificare un attributo e
restituire in ultimo la classe stessa, operazione necessaria per
realizzare la classe in un modulo (file) a parte.
-- metodo setter

function miaClasse.set_valore(self, nuovoval)

self.valore = nuovoval

end
-- metodo getter

function miaClasse.get_valore(self)

return self.valore

end

-- chiusura del modulo di classe

return miaClasse

La classe è pronta e non ti resta che testarla. Per fare questo è necessario
scrivere del codice nel file main.lua.

1. La prima operazione consiste nell’inclusione della classe appena


realizzata, questo avviene esattamente come per i moduli visti in
precedenza. A questo punto sei pronto per la creazione dell’istanza del
tuo primo oggetto. Crea un puntatore a oggetto e assegnalo al
costruttore passando come parametro il valore che desideri.

-- inclusione della classe


local miaClasse = require( "miaClasse" )

-- istanza di un oggetto attraverso il costruttore


local num = miaClasse.new(9)

2. Non ti resta che visualizzare nella consolle del simulatore i tuoi frutti;
questa operazione è possibile passando alla funzione print il metodo
get che di fatto recupera il valore dell’attributo. Prova poi a modificare
il valore con il metodo set e ristampalo a video nuovamente.

-- invocazione compatta del metodo. Equivalente a


oggetto.metodo(oggetto, [parametro/i])
print(num:get_valore())
num:set_valore(10)

print(num:get_valore())

Migliorare la sintassi del costruttore e aggiungere


un attributo oggetto
Hai fatto il primo passo, la creazione di una classe con il linguaggio LUA di
Corona SDK. La programmazione ad oggetti è molto potente e una volta
appresa e approfondita ti permetterà di sviluppare applicazioni molto più
complesse e performanti, inoltre imparerai a scrivere codice riutilizzabile in
futuri progetti.
Un primo miglioramento che puoi apportare al tuo progetto è quello di
semplificare il costruttore rendendo implicita la chiamata del metodo new.

1. Apri il file miaClasse.lua e aggiungi il seguente codice subito dopo


aver creato la tabella di classe e il cortocircuito.

-- tecnica per mascherare la parola chiave new


setmetatable(miaClasse, {
__call = function (cls, ...)
return cls.new(…)
end,

})

2. Un’altra caratteristica molto potente delle classi è quella di poter


aggiungere ad esse come attributi degli oggetti di altre classi, comprese
quelle di fabbrica del framework. In questo esempio operativo
aggiungerai un attributo immagine “visual” con le coordinate di
inserimento e il nome del file. Valori che saranno poi passati al
costruttore quando è istanziato un oggetto.
self.visual = display.newImage(file, posx, posy, true ) --
aggiunta di un attributo oggetto
3. Modifica infine i metodi getter e setter con la nuova sintassi che
prevede i due punti tra il nome dell’oggetto (classe) e il metodo.

-- metodo setter
function miaClasse:set_valore(nuovoval) -- aggiunta
dei due punti
self.valore = nuovoval
end

-- metodo getter
function miaClasse:get_valore()
return self.valore
end

Apri ora il file main.lua e modifica l’invocazione del costruttore di istanza


di un nuovo oggetto, omettendo il metodo new e passando come parametri
le caratteristiche dell’immagine che desideri visualizzare oltre al numero.
Local num = miaClasse(9,”Immagini/orol.png”,200,200)

Primo esempio di ereditarietà


Una delle caratteristiche più importanti nella programmazione ad oggetti è
l’ereditarietà. Per farti capire cos’è l’ereditarietà prova a figurarti il
seguente esempio: pensa a una classe che descrive una disciplina sportiva,
questa può avere una sede, un nome etc. Da questa classe potrebbe ereditare
una classe che descrive una particolare disciplina che eredita dalla classe
padre tutti i metodi comuni alle discipline sportive, ma poi specializza le
particolarità dello sport preso in considerazione. Nel linguaggio LUA di
Corona SDK è possibile implementare il meccanismo dell’ereditarietà in
diversi modi, quello che studierai ora è un primo approccio.

1. Migliora ulteriormente il costruttore in modo che possa a tutti gli


effetti replicare il meccanismo della OOP. Modifica il costruttore
rinominandolo in “init” e definendo esternamente a esso la metatable
con la seguente sintassi:

setmetatable(miaClasse, {
__call = function (cls, ...)
local self = setmetatable({}, cls)
self:init(...)
return self
end,
})

2. A questo punto non ti rimane che realizzare il nuovo costruttore con


l’indicazione dei due punti e l’inizializzazione degli attributi.

function miaClasse:init(init,file,posx,posy)
self.valore = init
self.visual = display.newImage(file, posx, posy, true )
-- aggiunta di un attributo oggetto
return self
end

Ottimo lavoro, è arrivato adesso il momento di realizzare la tua prima classe


derivata. Crea quindi un nuovo file nominandolo miaClassederivata.LUA.
Per effettuare questa operazione in modo veloce puoi anche duplicare un
file esistente, rinominarlo e ripulirlo dal codice.

1. La prima operazione consiste nell’importare la classe padre


“miaClasse”; effettuata questa operazione il codice rimanente della
creazione della classe e del metatable è quasi identico a quello già
visto e ottimizzato con la classe semplice, dovrai aggiungere solo un
collegamento alla classe padre attraverso l’operatore __index.
local miaClasse = require( "miaClasse" )
local miaClasseDerivata = {}
miaClasseDerivata.__index = miaClasseDerivata

setmetatable(miaClasseDerivata, {
__index = miaClasse, -- Meccanismo di ereditarietà
__call = function (cls, ...)
local self = setmetatable({}, cls)
self:init(...)
return self
end,
})

2. Il costruttore della classe derivata è simile a quello della classe padre


dalla quale eredita le funzionalità ed estende un attributo istanziandone
un secondo numero. Il primo numero viene invece istanziato dal
costruttore della classe padre assieme all’immagine direttamente
all’interno del costruttore della classe derivata. Il metodo di classe
effettua una somma dell’attributo generato dalla classe padre con
quello generato dalla classe figlio.

function miaClasseDerivata:init(init1,init2,file,posx,posy)
miaClasse.init(self,init1,file,posx,posy) -- chiamata
del costruttore ereditato
self.valore2 = init2
end

function miaClasseDerivata:get_valore() -- re definizione


del metodo
return self.valore + self.valore2
end

return miaClasseDerivata
Anche la classe derivata è pronta, non ti rimane che testare questo ultimo
codice andando a modificare il file main.lua

1. Importa nel file main anche la classe derivata.

local miaClasse = require( "miaClasse" )


local miaClasseDerivata = require( "miaClasseDerivata" )

2. Crea ora un secondo oggetto, chiamato “numeri” istanziato con il


costruttore della classe derivata: a questo metodo passerai due numeri
e le informazioni per l’immagine. Attraverso il polimorfismo,
invocando la funzione “get_valore” ridefinita nella classe derivata è
possibile recuperare il valore somma dei due numeri istanziati. La
funzione “set_valore” ridefinita ha invece il compito di modificare il
valore dell’attributo della classe derivata ovvero il secondo numero.
function miaClasseDerivata:init(init1,init2,file,posx,posy)

miaClasse.init(self,init1,file,posx,posy) -- chiamata
del costruttore ereditato
self.valore2 = init2
end

function miaClasseDerivata:get_valore() -- re definizione


del metodo
return self.valore + self.valore2
end

Secondo approccio

Avvia un nuovo progetto e quindi duplica il file main.lua rinominandolo in


miaClasse.lua.
1. Inizia a editare il file appena creato creando l’istanza di classe, a essa
puoi passare i parametri per inizializzare i tuoi oggetti, in questo
esempio un unico parametro (init). Realizza poi una nuova tabella self,
all’interno di essa potrai creare gli attributi pubblici ovvero gli attributi
che potranno essere modificati accedendo anche esternamente alla
classe. Questi attributi possono essere inizializzati con un valore
costante oppure con un parametro passato all’istanza della classe.

local function miaClasse(init)


-- nuova istanza
local self = {
-- attributi pubblici
num_pub = 10
}

2. Crea ora i tuoi attributi privati. Questi attributi, inizializzabili con una
costante oppure un parametro passato all’istanza della classe, sono
privati e interni alla classe. La classe garantisce la loro integrità; per la
lettura di suddetti valori è necessaria la realizzazione di un metodo di
classe, come vedrai più avanti.

-- attributi privati
local num_prv = init

Dopo aver realizzato gli attributi ti concentrerai sui metodi, definendo


prima quelli provati e poi i pubblici.

1. I metodi provati sono interni alle classi e permettono di realizzare


funzionalità interne alla classe. Solitamente sono invocati da metodi
pubblici, questa tecnica permette di semplificare e modulare il codice
ancora più nel profondo. Questo metodo di esempio prende due
parametri e ne calcola la media (media binare). I parametri che un
metodo pubblico potrà passare al metodo privato durante l’invocazione
possono essere un qualsiasi mix di attributi pubblici o privati,
rispettando il numero dei parametri.

-- metodi privati
local function media_binaria(var_a,var_b)
local mediab = (var_a+var_b) / 2
return mediab
end

2. I metodi pubblici sono la vera interfaccia tra l’oggetto e il mondo


esterno. Attraverso essi è possibile interagire con l’oggetto istanziato
con le funzionalità messe a disposizione dalla classe. Il primo metodo
pubblico di esempio mostra come sia possibile invocare un metodo
privato (appena definito) all’interno di esso, oltre a “ritornare” la
somma degli attributi pubblico e provato. Da notare che all’interno
della classe gli attributi pubblici sono sempre preceduti dalla parola
chiave self.

-- metodi pubblici
function self.somma()
print(media_binaria(self.num_pub,num_prv) )
return self.num_pub + num_prv
end

3. Il metodo incrementa permette di sommare una unità all’attributo


privato della classe.

function self.incremento()
num_prv = num_prv + 1
end
4. Il metodo getNum restituisce l’istanza dell’attributo privato
esternamente alla classe ed è l’unico modo per accedere a tale valore,
se non viene implementato non è possibile un accesso al valore
specifico. Questo garantisce una reale protezione dei valori privati
interni alla classe.

function self.getNum()
return num_prv
end
5. Per terminare il codice di classe è necessario “ritornare” self e il nome
stesso della classe attraverso la funzione “return”.

-- ritorno istanza
return self

end

-- ritorno classe
return miaClasse

Una volta realizzata la classe, implementerai nel file main.lua una sua
istanza andando così a creare un oggetto del tipo della classe. Utilizzerai
quindi i metodi pubblici di essa, potrai anche accedere in modo molto
semplice agli attributi pubblici mentre ti sarà negato di modificare quelli
privati, che possono essere inizializzati solo in fase di creazione se è
previsto il passaggio di un parametro.

1. La prima operazione consiste nella importazione della classe mediante


la funzione “require”.

-- inclusione della classe


local miaClasse = require( "miaClasse" )
2. Istanzia ora un nuovo oggetto passando a esso un parametro, così
come è previsto dalla classe.
local num = miaClasse(5)

3. Invoca ora per l’oggetto appena creato il metodo “somma” passato


come parametro alla funzione print. La funzione somma stamperà
nella consolle la media e la somma degli attributi pubblico e privato.
Invoca poi la funzione incremento, essa incrementerà di una unità
l’attributo privato.

print(num.somma())

num.incremento()

4. Le successive tre righe di codice serviranno a dimostrarti come è


semplice accedere in scrittura e lettura agli attributi privati, stampando,
modificando e ristampando in consolle il valore modificato
dell’attributo.

print(num.num_pub)

num.num_pub = 20

print(num.num_pub)

5. Le ultime due righe di codice invece ti dimostreranno in modo


inequivocabile come non sia possibile accedere direttamente a un
attributo privato di una classe, ma solo attraverso un metodo di classe
opportunamente realizzato.
print(num.num_prv)

print(num.getNum() )

Secondo approccio implementando l’ereditarietà


Realizzare classi con questa metodologia permette in egual misura di
implementare il meccanismo dell’ereditarietà e quindi di estendere una
classe e poterne ridefinire tutti o alcuni metodi all’occorrenza. Osserva
come questo approccio è possibile.

1. Crea un nuovo file nominandolo “miaClasseDerivata.lua”. La prima


operazione consiste nell’inclusione della classe genitore attraverso la
funzione “require”.
local miaClasse = require( "miaClasse" )

2. A questo punto puoi creare la classe estesa passando i parametri della


classe genitore e quelli che desideri estendere. Per innescare il
meccanismo di ereditarietà dovrai poi collegare a “self” la classe
genitore, passandole il parametro ad essa indirizzato.

local function miaClasseDerivata(init, init2)


local self = miaClasse(init) -- meccanismo ereditarietà

3. A questo punto hai la piena facoltà di implementare i tuoi attributi


pubblici preceduti dalla parola chiave “self” e quelli privati sia con
parametri in fase di inizializzazione che con valori costanti o entrambi
i casi con un operatore or (es: local num_prv = init2 or 50). In questo
caso, se non viene passato il parametro, l’attributo sarà istanziato con
un valore di default.

self.num_pub = 30

local num_prv = init2

4. Per accedere agli attributi privati della classe genitore è necessario


invocare la loro funzione di accesso preceduta dalla parola “self”.
Questo puntatore così realizzato permette di fatto di accedere a tale
funzione nella classe figlia.
local base_GetNum = self.getNum
5. Adesso è possibile a tutti gli effetti ridefinire la funzione “getNum” e
specializzarla a seconda delle esigenze. In questo caso effettua la
somma di tre valori: l’attributo privato e quello pubblico della classe
estesa e l’attributo privato della classe padre, restituendo all’esterno il
valore calcolato.

function self.getNum()
return num_prv + self.num_pub + base_GetNum()
end

6. L’ultimo passaggio, come per la classe padre, consiste nel ritorno di


self e della funzione di classe attraverso la parola chiave “return”.

return self
end

return miaClasseDerivata

Molto bene, non ti resta che implementare anche la classe derivata


all’interno del main e testare per accertarti che tutto sia andato a buon fine.

1. Importa la classe derivata.


local miaClasseDerivata = require ("miaClasseDerivata")

2. Crea un oggetto nuovo con la classe derivata.


local numDer = miaClasseDerivata(10,20)

3. Invoca all’interno di una print il metodo ridefinito dalla classe


derivata, poi incrementa l’attributo privato della classe con il metodo
“incrementa” ereditato dalla classe genitore e ripeti la print nello stesso
modo per constatare il corretto esito.
print(numDer.getNum())

numDer.incremento()

print(numDer.getNum())
6. Le coroutine
Introduzione

Una coroutine è simile a un processo in un sistema multitasking. Essa


possiede un suo ambiente con stack, variabili locali e istruzioni. Ma il
grande vantaggio di questo strumento è che può accedere a tutto quanto c’è
di globale nel tuo programma e può condividere stati e variabili con altre
coroutine, queste caratteristiche la rendono uno strumento collaborativo.
L’utilizzo delle coroutines non è banale, richiede attenzione e pratica ma se
ben sfruttato può diventare un mezzo di incredibile potenza.

Primo approccio

Creare una coroutine è molto semplice. Basterà creare un puntatore e


utilizzare la chiamata “coroutine.create” seguita dal codice della coroutine.
Il codice all’interno della chiamata è una funzione anonima, ovvero una
funzione che non possiede un nome ma che potrà ricevere parametri in
ingresso e fornire all’uopo parametri in uscita. All’interno della funzione
anonima è possibile inserire tutto il codice che si desidera come in una
normalissima funzione. L’esempio che segue realizza una coroutine che
semplicemente stampa una stringa nella consolle. La funzione “print(c1)”
visualizza l’indirizzo esadecimale del puntatore in memoria. Per avviare
una coroutine è necessario utilizzare la chiamata “coroutine.resume(nome
coroutine)”. Questa chiamata porterà la coroutine dallo stato iniziale
“suspended” allo stato “running” e finita l’esecuzione lo stato terminante
sarà “dead”. Esiste anche un quarto stato “normal” che però affronterai in
esempi successivi.
Osserva il codice:

-- la funzione è anonima
c1 = coroutine.create(function () print ("ciao mondo") end) --
Creazione di una coroutine(parametro una funzione)

print(c1) -- Indirizzo puntatore coroutine

print(coroutine.status(c1)) -- stati: suspended, running, dead,


normal

coroutine.resume(c1)

print(coroutine.status(c1)) -- fine esecuzione

Osserva ora un esempio più articolato dove all’interno della funzione


anonima aggiungerai un pizzico di complessità, ovvero un ciclo “for” che
genera la stampa di dieci numeri in progressione.

c2 = coroutine.create(function ()
for i=1, 10 do
print("c2",i )
end
end)
coroutine.resume(c2)

Adesso viene il bello: alla funzione di prima integrerai una nuova chiamata
“coroutine.yield(nome coroutine)”. Questa importantissima chiamata
sospende l’esecuzione della coroutine e per farla ripartire è necessario
invocare per essa la chiamata “coroutine.resume(nome coroutine)”. Come
puoi intuire questo meccanismo è di cruciale importanza perché ti permette
di sospendere o eseguire una o più coroutine dal programma principale, di
fatto controllandone il flusso a tuo piacimento.
Osserva il seguente codice:

c3 = coroutine.create(function ()
for i=1, 10 do
print("c3",i )
coroutine.yield(c3) -- sospende la funzione
end
end)
coroutine.resume(c3)

print(coroutine.status(c3))
coroutine.resume(c3)

Esempi notevoli

Hai appena appreso i rudimenti e le tecniche base delle coroutine, ora ti


concentrerai su quattro esempi che mostrano come è possibile passare
parametri a una coroutine oppure ricevere parametri.
Il primo esempio è una coroutine che prende in ingresso tre parametri, li
elabora e ne stampa il risultato sulla consolle con una funzione “print”. Il
passaggio avviene attraverso la chiamata “coroutine.resume(nome
coroutine, parametri … )”.
Osserva il seguente codice:

-- Esempio uno

esc1 = coroutine.create(function (a, b, c)

print("esc1", a, b, c + 7)
end)

coroutine.resume(esc1, 1, 2, 3)

Il secondo esempio è simile al primo, la coroutine prende in ingresso due


parametri, ma diversamente dal primo esempio è la chiamata
“coroutine.yield” a trattare l’output. In questo caso la chiamata oltre a
interrompere la coroutine restituisce un valore “true” e il corretto output
delle variabili in ingresso processate secondo il codice all’interno delle sue
parentesi tonde. È possibile quindi propagare dati in ingresso anche con la
chiamata “coroutine.yield”.
-- Esempio due

esc2 = coroutine.create(function (a, b)


coroutine.yield(a + b, a - b)
end)

print(coroutine.resume(esc2, 30, 10))

Il terzo esempio mostra come sia possibile creare un filtro con la chiamata
“coroutine.yield” sulle variabili passate in ingresso. Il corretto passaggio dei
parametri viene processato dalla prima print nel blocco della coroutine.
Passaggi di parametri diversi possono essere propagati invece dalla
chiamata “coroutine.yield”.
-- Esempio tre

esc3 = coroutine.create(function (x)


print("resume normale:", x)
print("resume argomenti extra:",coroutine.yield())
end)

coroutine.resume(esc3, "Ciao mondo")


coroutine.resume(esc3, 10, 20)

Il quarto esempio mostra il funzionamento della parola chiave “return”


all’interno della funzione anonima della coroutine. Questo funzionamento è
molto simile alla chiamata “coroutine.yield” ma in questo caso la funzione
termina naturalmente ritornando al corpo del programma i valori restituiti
dalla “return”.
-- Esempio quattro
esc4 = coroutine.create(function ()
return 7, 9
end)

print(coroutine.resume(esc4))

È arrivato il momento di studiare un esempio più complesso: produttore,


consumatore. In questo problema un ricevitore (consumatore) può delegare
a un produttore l’esecuzione di un compito, il produttore eseguirà il
compito richiesto invocando una terza funzione di invio, questa funzione
attraverso la chiamata “coroutine.yield” potrà interrompere il produttore e
propagare al consumatore il risultato dell’elaborazione. Risultato che sarà
assegnato a due variabili distinte: status (true oppure false) se l’operazione è
andata a buon file, e valore (il valore numerico dell’operazione delegata
nell’esempio di questo codice).

1. Il ricevitore (consumatore) avvia il produttore attraverso una chiamata


“coroutine.resume” e restituisce con la return il valore generato.

function ricevitore() -- il ricevitore attiva il produttore


e recupera stato e valore
local status, valore =
coroutine.resume(produttore)
return valore
end

2. Il produttore nella coroutine avvia un ciclo “while” infinito che genera


numeri random da 1 a 99, a ogni generazione di un numero viene
invocata la funzione invio che interrompe il ciclo e propaga il risultato
al ricevitore (consumatore).

produttore = coroutine.create( -- il produttore produce un


valore e quindi invoca la funzione invia
function ()
while true do
local x = math.random(1, 99)
invio(x)
end
end
)

3. La funzione “invio” è di fatto il collettore tra produttore e


consumatore, attraverso la chiamata “coroutine.yield” blocca il
produttore e propaga al consumatore il valore generato. In questa
funzione sarebbe possibile, se lo si desidera, applicare anche dei filtri
come, ad esempio, un controllo sulla bontà dei dati generati
casualmente.

function invio(val) -- il produttore invia il valore e si


sospende
coroutine.yield(val)
end

4. L’ultima parte di codice sono due esempi di generazione numeri tra


consumatore e produttore e stampa di essi nella consolle.

local val1 = ricevitore()


print(val1)

local val2 = ricevitore()


print(val2)
7. Le librerie per il linguaggio LUA
Introduzione

Tutti i linguaggi hanno un set di librerie che estendono le funzionalità del


codice, rendendo disponibili funzionalità non previste nel set base delle
istruzioni. LUA non è da meno e le sue ricche librerie spaziano dalla
matematica alla gestione delle tabelle, alla gestione delle stringhe etc.
Scopo di questo capitolo è la presentazione di questi strumenti attraverso
alcuni esempi notevoli di codice.

La libreria math

La libreria matematica comprende un set standard di funzioni per le più


importanti operazioni matematiche: le funzioni trigonometriche (sin, cos,
tan, asin, acos, ecc.) le funzioni esponenziali e logaritmiche (exp, log, log10
ecc.), le funzioni di arrotondamento dei numeri ( floor, ceil). Gestione,
inoltre, dei numeri di grosse dimensioni e generazione di numero pseudo-
casuali.
Maggiori informazioni sulla libreria, oltre a un elenco dettagliato delle
funzioni con esempi, sono reperibili al seguente link: http://lua-
users.org/wiki/MathLibraryTutorial

Applicazione delle funzioni matematiche di


libreria
Osserva ora alcuni casi concreti di utilizzo delle funzioni di libreria math.
1. Il primo esempio riguarda la generazione di numeri casuali. Per avere
una generazione di numeri il più casuale possibile è bene generare un
seme attraverso l’orologio interno del device. Questo grazie alla
funzione randomseed al cui parametro viene passata un’altra funzione
(os.time). Le funzioni del sistema operativo saranno approfondite in un
paragrafo successivo del capitolo. A questo punto sei pronto per
generare i tuoi numeri casuali. Se utilizzi la funzione random senza
nessun parametro essa andrà a generare un numero casuale compreso
tra 0 e 1. Specificando un solo parametro numerico, il numero generato
sarà compreso tra 0 e quel numero. Altrimenti è possibile definire un
intervallo.
math.randomseed(os.time())

print(math.random()) -- numero random da 0 a 1

print(math.random(6)) -- numero random da 0 a 6

print(math.random(10,20)) -- numero random da 10 a 2°

2. Attraverso il blocco (do – end) è possibile ridefinire delle funzioni di


libreria, ad esempio se desideri effettuare dei calcoli trigonometrici in
gradi e non in radianti. Osserva il codice sottostante per comprendere
meglio come funziona la meccanica:

do
local sin,asin = math.sin, math.asin
local deg, rad = math.deg, math.rad
math.sin = function (x) return sin(rad(x)) end
math.asin = function (x) return deg(asin(x)) end
end

3. A questo punto sei pronto per utilizzare le funzioni ridefinite:

print(math.sin(30)) -- da gradi a radianti


print(math.asin(0.5)) -- da radianti a gradi

La libreria table

Introduzione
La libreria table è costituita da funzioni ausiliarie per manipolare vettori e
tabelle generiche. Essa mette a disposizione funzioni per l’inserimento e
l’estrazione di elementi da un vettore, l’ordinamento di un vettore e la
concatenazione di una stringa di elementi del vettore.
Maggiori dettagli e un elenco esaustivo della libreria sono presenti al
seguente indirizzo:
http://lua-users.org/wiki/TableLibraryTutorial

Alcuni esempi notevoli


Osserva alcuni esempi di codice nei quali la libreria viene utilizzata.

1. Crea per prima cosa una funzione che stampa il contenuto di un


vettore.

-- funzione stampa elementi del vettore

function printVett(vett)

for n in pairs(vett) do
print(vett[n])
end
end
2. Popola ora un vettore con cinque elementi, quindi attraverso la
funzione di libreria “insert” inserisci il numero 29 in terza posizione.
Stampa il vettore per verificare la correttezza di suddetta operazione e
quindi rimuovi l’elemento in quinta posizione con la funzione
“remove”. Come ultima operazione ordina il vettore attraverso la
funzione “sort”.

t = {10, 17, 3, 7, 9}

table.insert(t,3,29)

printVett(t)

table.remove(t,5)

printVett(t)

table.sort(t)

printVett(t)

Continua a sperimentare con la libreria.

1. Inserisci la funzione “compare” che restituisce il più grande di due


numeri. Ordina un generico vettore di elementi numerici con la regola
creata precedentemente nella funzione. Come hai potuto intuire è
possibile creare criteri di ordinamento ad hoc.

local function compare( a, b )


return a > b
end

table.sort(t,compare)

printVett(t)
2. Inserisci ora un vettore di stringhe e, attraverso la funzione “concat”,
concatena le stringhe in un’unica stringa prima di stamparla nella
consolle. Attraverso la funzione “maxn” puoi stampare il maggiore
numero di un vettore numerico e attraverso il simbolo “#” davanti a un
vettore puoi ricavarne la dimensione.

anagrafica =
{
"Mario",
"Rossi",
"40"
}

print(table.concat( anagrafica, ", "))

print(table.maxn(t))

print(#t)

La libreria string

La libreria string di LUA permette di elaborare le stringe alfanumeriche del


linguaggio. Le funzioni all’interno di essa sono molto ricche di funzionalità
ed è possibile manipolare e trattare il testo in modo anche molto complesso.
Maggiori informazioni sulla libreria sono reperibili al seguente URL:
http://lua-users.org/wiki/StringLibraryTutorial

Alcuni esempi notevoli


Avvia un nuovo progetto standard base, apri il il file main.lua e inizia a
digitare il seguente codice:
1. Contare il numero di caratteri all’interno di una stringa è
un’operazione molto semplice:
local stringa = "Ciao Mondo Stringa"

print(string.len(stringa))

2. Anche la ripetizione di uno o più caratteri è un’operazione immediata,


resa disponibile dalla funzione “rep”:
ripetizione = string.rep("ciao ",10)

print(ripetizione)

3. Attraverso la funzione sub è possibile estrapolare parte di una stringa,


specificando oltre al nome il valore di index di inizio e fine
sottostringa. Anche la formattazione del testo e dei numeri è possibile
e simile a quella di linguaggi storici come il C. Ad esempio il formato
“%.4f” permette di stampare una formattazione con quattro cifre
significative dopo la virgola.
print(string.sub(stringa,6,10))

print(string.format("pi = %.4f", math.pi))

4. È possibile cercare una sottostringa specifica, all’interno di una stringa


di più ampie dimensioni, attraverso la funzione “find”. Questa
funzione restituisce i valori di index iniziale e finale della sottostringa
all’interno della stringa di ricerca, è possibile poi sfruttare questi valori
come si vuole, ad esempio per stampare la sottostringa:
s = "le stringhe nel linguaggio lua"

i,j = string.find(s, "linguaggio")

print(string.sub(s,i,j))

5. Un’altra funzionalità molto comoda è data dalla funzione “gsub”, essa


permette di ricercare e sostituire particolari occorrenze di caratteri
all’interno di una stringa. È possibile sostituire nominativi, date ecc.
s1 = string.gsub("lua è un linguaggio potente", "potente",
"completo")

print(s1)

6. È possibile, attraverso la funzione “select”, contare anche l’occorrenza


di particolari caratteri come ad esempio lo spazio:
conteggio = select(2, string.gsub(s1, " ", " "))

print(conteggio)

7. Attraverso la funzione “match” e l’uso dei patterns (puoi approfondire


il significato e l’utilizzo dei patterns all’indirizzo specificato all’inizio
di questo paragrafo) è possibile estrapolare parti di stringa salvandoli
in distinte variabili, oppure in tabelle. Nell’esempio seguente è
presentata una tecnica per l’estrapolazione della data da una stringa,
memorizzando i singoli valori in tre distinte variabili. Questa è una
funzionalità molto potente.

data = "Oggi è il 03/06/2014"

g, m, a = string.match(data, "(%d+)/(%d+)/(%d+)")

print(g,m,a)

Le altre librerie del linguaggio LUA

La libreria I/O
La libreria I/O permette di manipolare i file, salvando le informazioni in
modo persistente. Essa lavora principalmente in due modalità: una semplice
e una completa. La modalità semplice lavora in modo asincrono su file di
input e output (scrittura/lettura). La modalità completa ha un approccio
molto più complesso e una strutturazione del lavori di tipo object oriented,
questa funzionalità è pensata per un uso massivo dei file.
Maggiori informazioni sulla libreria sono presenti al seguente URL:
http://lua-users.org/wiki/IoLibraryTutorial

La libreria del sistema operativo


La libreria del sistema operativo presenta un corposo set di istruzioni e
direttive per le più svariate funzionalità: ricavare data e orario di creazione
di un file, struttura del file system, lingua, versione del sistema operativo
ecc. Funzionalità più specifiche, come il networking, non sono presenti in
questa libreria in funzione della sua portabilità, ma è possibile usufruire di
librerie specifiche sviluppate in LUA.
Maggiori informazioni sulla libreria sono reperibili al seguente URL:
http://lua-users.org/wiki/OsLibraryTutorial

La libreria debug
Questa libreria non ha la pretesa di offrire un ambiente integrato di debug,
ma offre tutte le primitive necessarie alla costruzione di un ambiente di
debug personalizzato. Queste funzioni sono molto dispendiose in termini di
prestazioni ed efficienza ed è bene utilizzarle con parsimonia.
Maggiori informazioni sulla libreria sono reperibili al seguente URL:
http://www.lua.org/pil/23.html

Corona SDK mette a disposizione un certo numero di librerie che


estendono ulteriormente il linguaggio LUA. Alcune le hai utilizzate
esplicitamente durante le esercitazioni di questo ebook, un elenco
completo è presente al seguente URL: http://lua-
users.org/wiki/LibrariesAndBindings
8. Estendere LUA attraverso il linguaggio C++
Introduzione

Questo capitolo ti mostrerà la semplicità con cui è possibile estendere le


funzioni di LUA anche con altri linguaggi. In questo esempio verrà
utilizzato il C++ e la libreria “LUA.hpp” appositamente creata per esso.
Questa libreria ti permetterà di gestire la comunicazione tra il linguaggio di
scripting e il linguaggio C++ attraverso uno stack locale globale. Esistono
librerie anche per il C e altri linguaggi, ti invito a visionare il seguente link
per approfondimenti: http://www.lua.org/pil/26.html

Nota: questo capitolo presuppone una conoscenza minima del


linguaggio C++ e di come compilare un programma con esso. Se non
conosci il linguaggio e desideri approcciarti ad esso, un
approfondimento è reperibile al seguente URL:
http://www.html.it/guide/guida-c2/

Invocare funzioni C++ da LUA

Per testare il seguente codice avrai necessariamente bisogno di un


compilatore C++, questa operazione è possibile con tutti i sistemi operativi
(XCode per Mac, Visual Studio per Windows e il compilatore C++
integrato nelle macchine Linux e Unix). Ricordati di importare nelle librerie
la “LUA.hpp” scaricabile con il pacchetto LUA al seguente indirizzo:
http://www.lua.org/download.html
Osserva ora il seguente codice generato con C++.

#include <lua.hpp>
#include <iostream>
int add(lua_State *L);

int main()

lua_State *L = luaL_newstate( );
luaL_openlibs(L);

lua_pushcfunction(L, add);
lua_setglobal(L, “add”);

luaL_dofile(L, “test.lua”);

lua_getglobal(L, “x”);

std::count << lua_tonumber(L, -1) << std::endl;

lua_close(L);

return 0;

int add(lua_State *L)

double n1 = lua_tonumber(L, 1);


double n2 = lua_tonumber(L, 2);

return 1;
}

Come puoi notare tutta la logica del programma è all’interno della funzione
main ed è in essa che viene definita e attivata la funzione “add” per LUA.
La funzione “add” per comunicare con LUA utilizzerà uno stack locale
speciale chiamato L, questo oggetto viene dichiarato e inizializzato
all’inizio della funzione main nel seguente modo ed è sempre
indispensabile: lua_State *L = LUAL_newstate( );

A questo punto dovrai passare l’oggetto appena creato alla funzione


gestione librerie per LUA nel seguente modo: luaL_openlibs(L);

Solo a questo punto potrai aggiungere la funzione con il suo relativo stack
alla libreria e renderla globale con il seguente codice:

lua_pushcfunction(L, add);
lua_setglobal(L, “add”);

Bene, accorpa ora al tuo codice il file LUA che invocherà la funzione e
rendi globale la variabile da essa risultante nell’assegnazione del valore di
ritorno, questo valore ti servirà per una stampa in consolle tramite
streaming.

luaL_dofile(L, “test.LUA”);

lua_getglobal(L, “x”);

std::count << lua_tonumber(L, -1) << std::endl;

Chiudi ora il buffer dello stack condiviso e termina il main.

lua_close(L);
return 0;

Osserva ora il codice della funzione “add”, puoi notare che lo stack L come
ti aspettavi è il candidato per il passaggio dei parametri e l’assegnazione dei
medesimi all’interno della funzione. Nell’esempio qui riportato la funzione
“lua_tonumber” ha lo scopo di convertire un valore stringa numerico in un
valore puramente numerico.
Ricorda che per terminare correttamente la funzione è necessario eseguire
l’istruzione return seguita dal valore 1.

int add(lua_State *L)

double n1 = lua_tonumber(L, 1);


double n2 = lua_tonumber(L, 2);

return 1;
}

Prima di testare la tua nuova funzione C++ per LUA, ricordati di aprire un
nuovo file LUA (ad esempio test.lua) con la seguente sintassi:
x = add(13, 17)

Se tutto è andato a buon fine l’output nella consolle sarà esattamente la


somma dei due numeri, ovvero 30.

Conclusione

Questo breve assaggio ti ha mostrato com’è semplice e intuitivo creare


funzioni in un altro linguaggio e importarle dentro a LUA. Se desideri
approfondire la tecnica che esula da questa guida, pensata per un
apprendimento del linguaggio base, ti invito a consultare il web e il sito di
riferimento di LUA.
L’autore

Mirco Baragiani è nato a Bologna il 15/01/1975. Appassionato di


informatica e tecnologia fin dall’infanzia è un libero professionista e
collabora attivamente in veste di editor ed autore con la casa editrice
Area51 Publishing, per la stessa ha già pubblicato l’ebook “50 trucchi per
iPad”, l’ebook “Siri: La guida definitiva”, l’ebook “Programmazione C: le
basi per tutti” e la collana in 10 ebook “Corso di Corona SDK”. È l’autore
dell’opera in fascicoli “Corso di programmazione iPhone, iPad e Mac”
edito da Hobby&Work giunto alla quarta ristampa e ha coordinato in veste
di editor l’opera “Programmare app per Mobile” sempre per lo stesso
editore. Si occupa anche di progettazione e sviluppo software per sistemi
mobile Apple e Android.
Parallelamente all’informatica si diletta anche con l’arte contemporanea e
ricerca nell’ambito del pensiero creativo attraverso svariate risorse digitali e
analogiche.
Esperto in un click
Indice
Introduzione
1. Installare e configurare LUA
Introduzione
Installare e configurare l’IDE
Conclusione
2. Introduzione al linguaggio
Prefazione
Le variabili
Variabili globali
Variabili locali
Campi di tabelle
Commentare il codice con LUA SDK
La corretta scelta per il nome delle variabili
Tipi di dato primitivo in LUA
If Then Else
Espressioni
Operatori Aritmetici
Operatori relazionali
Operatori logici
Operatore concatenazione di stringhe
Operatore lunghezza di una stringa
Precedenza degli operatori con LUA
Le stringhe nel linguaggio LUA
Cicli
Il ciclo for
Ciclo for generico
Il ciclo while
Il ciclo repeat
Tabelle
3. Metatabelle e metametodi
Esempio operativo
Soluzione esercizio
Metametodi relazionali
Metametodi di accesso alle tabelle
Tabelle con valori di default
Tracciare l’accesso alle tabelle
Tabelle di sola lettura
4. L’ambiente
Introduzione
Variabili dinamiche con nomi globali
Tecnica per memorizzare variabili globali
Moduli e pacchetti
5. Programmare a oggetti con LUA
Introduzione
Primo approccio
Migliorare la sintassi del costruttore e aggiungere un
attributo oggetto
Primo esempio di ereditarietà
Secondo approccio
Secondo approccio implementando l’ereditarietà
6. Le coroutine
Introduzione
Primo approccio
Esempi notevoli
7. Le librerie per il linguaggio LUA
Introduzione
La libreria math
Applicazione delle funzioni matematiche di libreria
La libreria table
Introduzione
Alcuni esempi notevoli
La libreria string
Alcuni esempi notevoli
Le altre librerie del linguaggio LUA
La libreria I/O
La libreria del sistema operativo
La libreria debug
8. Estendere LUA attraverso il linguaggio C++
Introduzione
Invocare funzioni C++ da LUA
Conclusione
L’autore

Potrebbero piacerti anche