Principles of Programming Languages Introduction to Lua Ryhmä 10
1 Johdanto Lua (lausutaan /ˈlu.a/ ja tarkoittaa kuuta Portugalin kielellä) on Brasiliassa vuonna 1993 kehitetty skriptikieli, joka erikoistuu sulautettavuuteen ja suorituskykyyn. Sen on suunnitellut ja toteuttanut Pontifical Catholic University of Rio de Janeiro, jossa sitä edelleen ylläpidetään. Tässä kirjoitelmassa käymme läpi Luan perusominaisuudet. Luvussa kaksi esitellään joitakin tunnettuja Luan käyttökohteita. Luku kolme puolestaan keskittyy kielen toteutukseen ja teknisiin ominaisuuksiin. Lopuksi luvussa neljä esitellään kielen syntaksia. 2 Käyttökohteet Lualla on useita käyttökohteita; aina pelien laajennoksista autoihin. Vaikka käyttökohteita on useita, on suurin paino peleissä sekä erinäisten ohjelmien liitännäisissä. Taulussa 2.1 on listattuna eri kategorioita, missä Luaa on käytetty. Taulukko 2.1 Luan käyttökohteita Kategoria Ohjelmat Autot Tietokannat/Tietokantojen hallinta Pelimoottorit Pelit Kuvien käsittely CMS Verkon diagnostiikka Serverit Missä Adobe Lightroom Mercedes-Benz käyttää Luaa useassa automallissa MySQL Proxy ja MySQL Workbench Aleph One, LÖVE, CryEngine Total War, World of Warcraft GIMP MediaWiki Wireshark Apache, nginx, lighttpd Erityisesti pelisovelluksissa Luan käyttöä perustellaan helpolla muokattavuudella ja prototypioinnilla. Se myös mahdollistaa käyttäjien itsensä tekemät muokkaukset pelien käyttöliittymiin (World of Warcraft) tai jopa uuden sisällön luomisen peliin (Garry s Mod). 3 Tekniset ominaisuudet Lua on skriptikieli, joka tukee useaa ohjelmointiparadigmaa kuten olio-ohjelmointi, funktionaalinen ohjelmointi ja proseduraalinen ohjelmointi. Lua-koodia kirjoitetaan dynaamisesti ja sitä ajetaan Javan tapaan kääntämällä kirjoitettua koodia tavukoodi käskyiksi, tai ns. toimintakoodiksi, jota prosessoidaan erillisessä virtuaalikoneessa. Toisin kuin Javassa, Luan virtuaalikone ei ole pino-pohjainen vaan rekisteripohjainen. Lua on toteutettu C-kirjastoksi ja täten se on helppo "sulauttaa" mukaan toiseen koodiin. Tämän ominaisuuden ansiosta Lua on määritelty laajennus kieleksi (extension language).
3.1 Tyypit ja ympäristöt Luan tyypit ovat dynaamisia. Tämä tarkoittaa, että itse muuttujilla ei ole tyyppiä mutta arvoilla on. Kaikki arvot ovat ns. First-Class arvoja eli kaikkia arvoja voidaan välittää parametrina, sijoittaa muuttujiin ja palauttaa paluuarvona. Kielessä on kahdeksan perustyyppiä: nil, boolean, number, string, function, userdata, thread ja table. Nil on Luassa sama kuin monessa muussa kielessä null eli sillä kuvataan arvon puuttumista. Nil tyyppi voi saada arvokseen ainoastaan yhden arvon: nil. Boolean kuvaa totuusarvoa ja voi saada arvot true ja false; nil ja false tekevät ehdosta epätoden, muut arvot toden. Number tyyppi voi saada arvokseen niin kokonaislukuja kuin liukulukuja. String tyyppi kuvastaa kahdeksen bittistä muuttumatonta merkkijonoa. Function tyyppi voi saada arvokseen joko Lualla kirjoitetun funktion tai C:llä kirjoitetun funktion. Userdata kuvaa raakaa muistilohkoa. Tämä voi olla joko oikea muistilohko tai C osoitin arvo. Thread nimensä mukaisesti kuvaa yksittäisiä säikeitä. Näiden avulla voidaan toteuttaa rinnakkaisuutta, josta lisää kappaleessa 3.5. Table tyyppi kuvaa tauluja, joita voi indeksoida millä tahansa arvolla paitsi nil. Luassa taulut ovat ainoa tietorakenne mekanismi. Taulujen avulla kuvataan kaikki tarvittava kuten esimerkiksi normaalit taulut, graafit, puut sekä sekvenssit. Luassa on käytössä ympäristöt. Jokainen muuttuja, joka määritellään, lisätään tauluun nimeltä _ENV. _ENV taulut ovat sen hetkisen näkyvyysalueen muuttujat. Koko ohjelman kattavat globaalit muuttujat lisätään _G tauluun. 3.2 Virheidenhallinta Virheidenkäsittely on Luassa toteutettu seuraavasti: isäntäohjelma kutsuu C-koodin avulla funktiota Lua kirjastosta ja mikäli virhe tapahtuu, kontrolli palautetaan isäntäohjelmalle, joka voi tehdä tarvittavat toimenpiteet saadulle virheelle. Luassa on erillinen error funktio, jota kutsumalla voidaan itse generoida virhe ja virhe teksti. Virheitä voi ottaa kiinni käyttämä funktioita pcall ja xpcall. Luan itsensä generoimat virheet ovat aina merkkijonoja, jotka sisältävät virheen syyn. 3.3 Metataulut Luassa jokaisella arvolla voi olla metataulu. Nämä taulut ovat normaaleja tauluja, joissa määritellään arvon käyttäytyminen halutuissa operaatiossa. Näitä metataulun operaatioita kutsutaan metametodeiksi. Metametodien aivan/indeksi taulussa on string tyyppinen kuvaus tapahtumasta, jonka alussa on kaksi alaviivaa; esim. yhteelasku operaatio add. Metataulujen muokattavat metametodit on esitetty taulussa 3.1.
Taulukko 3.1 Metataulun metametodit. Avain Operaatio sub Vähennys (-) operaatio mul Kerto (*) operaatio div Jako (/) operaatio mod Modulo (%) operaatio pow Eksponentti (^) operaatio unm Negaatio (unaari -) operaatio idiv Lattia jako (//) operaatio band Bitwise AND (&) operaatio bor Bitwise OR ( ) operaatio bxor Bitwise XOR (binääri ~) operaatio bnot Bitwise NOT (unaari ~) operaatio shl Bitwise left shift (<<) operaatio shr Bitwise right shift (>>) operaatio concat Ketjutus (..) operaatio len Pituus (#) operaatio eq Yhtäsuuruus (==) operaatio lt Pienempi kuin (<) operaatio le Pienempi tai yhtäsuuri kuin (<=) operaatio index Indeksointi määritys - taulu[avain] newindex Indeksointi sijoitus - taulu[avain] = arvo call Call-operaatio - func(args) Taulukon 3.1 operaatiot ovat perusoperaatioita ja koska metataulut ovat normaaleja tauluja, voi ne sisältää muutakin tietoa, esimerkiksi roskienkeruuseen liittyvää. Metatauluja ja metametodeja voidaan hyvin verrata C++:n operaattorien uudelleenmäärittelyyn (Operator overloading). Vaikka toteutus on huomattavasti erilainen, on ajatus sama: kustomoidaan operaattorin toimintaa määrittelemällä sen toteuttava toiminto itse. 3.4 Roskienkeruu Luassa on käytössä automaattinen muistin hallinta. Käyttäjän ei tarvitse huolehtia muistin varaamisesta ja ennen kaikkea muistin vapauttamisesta. Tämä on toteutettu siten, että Lua ajaa roskienkeruuta, joka kerää ja poistaa ns. kuolleet objektit. Käyttäjällä on mahdollisuus myös vaikuttaa roskienkeräykseen säätämällä arvoja garbage-collector pause ja garbage-collector step multiplier. Näiden arvojen avulla määrätään kauanko roskienkeruu odottaa ennen seuraavaa sykliä sekä keruun nopeutta. Kuten aikaisemmin on mainittu, roskienkeruuta koskevia metametodeja voidaan asettaa tauluihin. Tällöin tulee objektit merkitä viimestelyyn. Tämä toteutetaan laittamalla metatauluun indeksi gc. Indeksiä vastaavan metametodin tulee olla funktio tai muutoin Lua sivuuttaa ko. kentän.
Roskienkeruu ei huomio heikkoja viittauksia. Täten objekti, johon on vain heikkoja viittauksia, kerätään roskiin. 3.5 Rinnakkaisuus Rinnakkaisuus on tuettuna Luassa. Luassa on taulu coroutine, jonka avulla voidaan luoda uusia thread tyypin objekteja. Nämä vastaavat suoritussäiettä, jonka suoritus päättyy kahdella tavalla: joko suorittamalla komennot loppuun tai manuaalisesti kutsumalla yield funktiota. Luan rinnakkaisuutta voi kontrolloida myös C-rajapinnan kautta. Se sisältää funktiot lua_newthread, lua_resume ja lua_yield, joiden avulla voidaan suoraan ohjata yksittäisen säikeen toimintaa. Rajapinnan toiminnasta kerrotaan lisää seuravaassa luvussa. 3.6 C-rajapinta Lua on laajennos kieli (extension language). Jotta muut kielet ja ohjelmat voisivat käyttää Luaa, on sitä varten tehty C-rajapinta. C-rajapinta on oikeastaan vain joukko C funktioita isäntäohjelmalle. Näiden funktioiden avulla isäntäohjelma pystyy kutsumaan Luaa. Monen muun C kirjaston tavoin, Lua rajapinta ei tee tarkistuksia annetuille parametreille vaan tämä on kehittäjän omissa käsissä. Kaikki arvot C:stä ja C:hen menee virtuaalisen pinon kautta. Pinon jokainen elementti kuvastaa Luan arvoa (nil, string jne). Kun C funktiota kutsutaan, luodaan uusi pino, joka on aivan erillinen muista pinoista. Pino sisältää aluksi kaikki C funktiolle tarkoitetut parametrit ja lopuksi myös C funktion palauttaman arvon. Rajapintaa käytettäessä kehittäjän on huomioitava, että käytetty pino on eheä. Tällä tarkoitetaan sitä, että on kehittäjän vastuulla tarkastaa, että pinossa on tarpeeksi tilaa puskea uutta tietoa. Kutsuttaessa C funktiota Lua luo pinon, jossa on 20 ylimääräistä elementtiä. 4 Syntaksi Perussyntaksi on Luassa hyvin C:n tapainen. Käännettävää koodia kutsutaan nimellä chunk, joka koostuu listasta sekventiaalisesti suoritettavia käskyjä, josta käytetään nimitystä block. Nämä kaksi määritelmää on esitetty BNF-notaatioissa 4.1 ja 4.2. Notaatiossa 4.2 stat tarkoittaa käskyä ja {stat} käskyistä koostuvaa listaa. chunk ::= block (4.1) block ::= {stat} (4.2) exp ::= nil false true Numeral LiteralString (4.3)... functiondef prefixexp tableconstructor exp binop exp unop exp
Käskyt voivat koostua esimerkiksi muuttujien määrittelyistä, funktiokutsuista, erilaisista kontrollirakenteista tai sijoitusoperaatioista, ja ne erotetaan toisistaan rivinvaihdolla. Käskyt voivat sisältää erilaisia lausekkeita, jotka kuvaavat esimerkiksi totuusarvoa, kokonaislukua, tai merkkijonoa. Peruslausekkeet on määritelty Luan dokumentaatiossa notaatiolla 4.3. Erilaisten operaatiolausekkeiden määrittelyyn palataan luvussa 4.3. 4.1 Muuttujat Luan muuttujat jaetaan paikallisiin ja globaaleihin muuttujiin. Globaalit muuttujat eivät tarvitse erillistä määrittelyä, vaan niihin voidaan vapaasti viitata koodissa. Paikalliset muuttujat puolestaan tarvitsevat erillisen määrittelykäskyn. Kolmanneksi muuttujatyypiksi luetaan taulukoiden kentät, joita myös tavalliset muuttujat käytännössä ovat (luku 3.1). Muuttujan määrittely on ilmaistu notaatiossa 4.4, jossa Name on muuttujan tunniste, ja kaksi jälkimmäistä määrittelyä ilmaisevat kaksi vaihtoehtoista tapaa käsitellä taulukoiden kenttiä. var ::= Name prefixexp [ exp ] prefixexp. Name (4.4) stat ::= local Name [ = exp ] (4.5) Paikallinen muuttuja määritellään antamalla sen tunniste notaation 4.5 mukaisesti. Käskyn avainsanalla local muttuja sidotaan käskyn näkyvyysalueelle paikalliseksi muuttujaksi. Näkyvyysalueen ulkopuolelta tunnisteeseen Name tehdyt viittaukset osoittavat kyseisen tunnisteen globaaliin muuttujaan, kun taas näkyvyysalueen sisällä tehdyt viittaukset osoittavat määriteltyyn paikalliseen muuttujaan. Sama paikallinen muuttuja voidaan myös määritellä useaan kertaan sisäkkäisillä näkyvyysalueilla. Tässä tapauksessa muuttujaan tehdyt viittaukset osoittavat jokaisella näkyvyysalueella aina lähimpään ulompaan muuttujaan, eivätkä sisempien muuttujien arvot vaikuta ulompien muuttujien arvoon käskyjen suorituksen aikana. Muuttujien näkyvyysalueisiin voidaan myös vaikuttaa määrittelemällä erillinen näkyvyysalue notaation 4.6 mukaisesti. stat ::= do block end (4.6) Paikalliselle muuttujalle voidaan määrittelyn yhteydessä myös antaa alkuarvo notaation 4.5 hakasulkeissa olevalla sijoituslausekkeella. Jos muuttujaa ei alusteta, se saa oletuksena arvon nil, joka on myös globaalien muuttujien alkuarvo. Muuttujaan sijoitus tapahtuu vastaavasti notaatiolla 4.7 stat ::= var = exp (4.7) namelist ::= Name {, Name} (4.8) explist ::= exp {, exp} (4.9) stat ::= local namelist [ = explist] (4.10) stat ::= varlist = explist (4.11) Lua tukee myös useampien muuttujien käsittelyä yhdellä käskyllä. Näin notaatioit 4.5 ja 4.7 voidaan yleistää notaatioiksi 4.10 ja 4.11 käyttäen notaatioita 4.8 ja 4.9. Tätä ominaisuutta voi hyödyntää erityisesti vararg-parametrien yhteydessä, jotka esitellään tarkemmin luvussa 4.5.
4.2 Kontrollirakenteet Lua sisältää C-kielen kanssa hyvin samanlaisia kontrollirakenteita. While-silmukat ja if-lauseet toimivat C- kielen kanssa täysin vastaavasti, ja repeat-until-silmukka vastaa C-kielen do-while-silmukkaa. Myös silmukan keskeyttävä break-käsky toimii vastaavalla tavalla C-kielen kanssa. Näiden rakenteiden syntaksi on kuvattu notaatiolla 4.12. stat ::= while exp do block end repeat block until exp (4.12) if exp then block {elseif exp then block} [else block] end break Ehtolauseissa arvot nil ja false tulkitaan totuusarvoksi false ja kaikki muut totuusarvoksi true. Repeatuntil-silmukan ehtolauseke voi lisäksi viitata silmukan sisällä määriteltyihin paikallisiin muuttujiin. Kielestä löytyvät myös goto ja label käskyt, joiden syntaksi on määritelty notaatioissa 4.14. Goto-käsky hyppää siihen label-käskyyn, jossa on sama notaatiolla 4.13 ilmaistu tunniste. Hypyn kohteena oleva label-käsky on tyhjä käsky, joka ei suorita mitään operaatiota. Label-käskyllä määritellyt tunnisteet noudattavat samoja näkyvyysperiaatteita kuin paikalliset muuttujat luvussa 4.1. label ::= :: Name :: (4.13) stat ::= label goto Name (4.14) Lua tarjoaa kaksi eri vaihtoehtoa for-silmukalle. Näistä ensimmäinen on numeerinen for, joka on esitelty notaatiossa 4.15. Se määrittelee silmukkamuuttujan, joka aloittaa sille määritellystä alkuarvosta ja jatkaa silmukan suorittamista, kunnes se läpäisee annetun ehtolausekkeen. Silmukalle voidaan antaa myös erillinen päivityslauseke, joka muuttaa silmukkamuuttujan arvoa jokaisen kierroksen päätteeksi. stat ::= for Name = exp, exp [, exp] do block end (4.15) stat ::= for namelist in explist do block end (4.16) Toinen vaihtoehto on geneerinen for, joka on määritelty notaatiossa 4.16 käyttäen luvun 4.1 notaatioita 4.8 ja 4.9. Tämä for-silmukka käyttää iteraattorifunktiota, joka tuottaa seuraavan arvon määritellyille silmukkamuuttujille jokaisen kierroksen jälkeen. Silmukka pysähtyy kun iteraattorifunktio tuottaa arvon nil. Geneerisen for silmukan yhteydessä käytetään yleensä Luan standardikirjastossa määriteltyjä apufunktioita (esimerkiksi pairs) helpottamaan silmukan käyttöä. 4.3 Lausekkeet Tässä luvussa käydään läpi lausekkeiden syntaksia ja eri operaatioihin liittyviä toiminnallisuuksia. Luan binääri- ja unaarioperaatiot on esitelty taulukossa 4.1 jaettuna bitti- ja vertailuoperaatioihin sekä loogisiin ja aritmeettisiin operaatioihin. Näiden lisäksi Lua sisältää ketjutusoperaation.. sekä pituusoperaation #. Operaatioiden suoritusjärjestus on kuvattu taulukossa 4.2. Ketjutus- ja eksponenttioperaatiot ovat oikealle assosiatiivisia, ja muut binäärioperaatiot ovat vasemmalle assosiatiivisia.
Taulukko 4.1 Binääri- ja unaarioperaatiot. Aritmeettiset operaatiot Bittioperaatiot Vertailuoperaatiot Loogiset operaatiot Summa + AND & Yhtäsuuruus == and Vähennys - OR Erisuuruus ~= or Kertominen * EXCLUSIVE OR ~ Pienempi < not Liukulukujako / RIGHT SHIFT >> Suurempi > Lattiajako // LEFT SHIFT << Pienempi tai <= Modulo % NOT (Unaari) ~ yhtäsuuri eksponentti ^ Suurempi tai >= Miinus (unaari) - yhtäsuuri Taulukko 4.2 Suoritusjärjestys Prioriteetti Operaatiot (Suurempi luku suoritetaan ensin) 1 or 2 and 3 < > <= >= ~= == 4 5 ~ 6 & 7 << >> 8.. 9 + - 10 * / // % 11 (unaarioperaatiot) not # - ~ 12 ^ Lua suorittaa operaatioiden vaatimat tyyppikäännökset automaattisesti suoritusaikana. Binäärioperaatoissa operandit muutetaan kokonaisluvuiksi ja liukulukujaossa sekä eksponenttioperaatioissa operandit muutetaan liukuluvuiksi. Muissa aritmeettisissa operaatioissa operandit muutetaan liukuluvuiksi, jos yksikin operandeista on liukuluku. Ketjutusoperaatio muuttaa aina operandit merkkijonoiksi ja palauttaa merkkijonon. 4.4 Taulukot Taulukot mahdollistavat monimutkaisempien tietorakenteiden luomisen. Taulukkoja voidaan tallentaa muuttujiin samalla tavalla kuin muitakin arvoja. Taulukon rakentajaa käytetään notaatioiden 4.17 4.20 mukaisesti. tableconstructor ::= { [fieldlist] } (4.17) fieldlist ::= field {fieldsep field} [fieldsep] (4.18) field ::= [ exp ] = exp Name = exp exp (4.19) fieldsep ::=, ; (4.20)
Taulukkoa voidaan indeksoida luvun 4.1 notaatiossa 4.4 annetuilla vaihtoehdoilla. Ensimmäinen vaihtoehto on C-kielen tapaan hakasulkeilla. Toisena vaihtoehtona on olio-ohjelmoinnille tyypillinen pistenotaatio. 4.5 Funktiot Luan funktiomäärittely tapahtuu notaatioiden 4.21 4.23 mukaisesti. Vaihtoehtona tarjotaan myös notaation 4.24 mukaiset määrittelykomennot. Funktiomäärittely on siis lauseke, jonka paluuarvo on tyyppiä function. stat ::= var = function funcbody (4.21) funcbody ::= ( [parlist] ) block end (4.22) parlist ::= namelist [,... ]... (4.23) stat ::= [local] function Name funcbody (4.24) Funktiokutsun yhteydessä annettujen parametrien lista sovitetaan automaattisesti funktiomäärittelyn parametrilistaan. Vararg-parametri (notaation 4.23... ) mahdollistaa funktioille vaihtelevan määrän parametreja. functioncall ::= prefixexp args (4.25) args ::= ( [explist] ) tableconstructor LiteralString (4.26) Funktiota kutsutaan notaatioiden 4.25 ja 4.26 mukaisesti. Jos notaation prefixexp-lauseke viittaa funktioon, käynnistetään vastaava funktiokutsu. Muussa tapauksessa kutsutaan kyseisen arvon callmetametodia. Lua mahdollistaa myös häntärekursiivisen funktiokutsun, kun funktiokutsun yhteydessä käytetään return-avainsanaa (return functioncall).