Programovací jazyk Lua

Při pohledu do technické specifikace některých her i dalších aplikací se můžeme dočíst, že pro vývoj byl mj. využit i skriptovací jazyk Lua. Jedná se o jednoduše použitelný programovací jazyk, který je skutečně mezi výrobci herního softwaru z několika příčin oblíbený. V této knížce si řekneme, proč tomu tak je.

Programovací jazyk Lua

Programovací jazyk Lua patří do poměrně rozsáhlé a stále častěji používané skupiny vysokoúrovňových skriptovacích jazyků, do níž můžeme zařadit například populární Python, Perl, Scalu, Ruby, na síle nabývající JavaScript či dnes již poněkud méně populární jazyk Tcl. Tyto programovací jazyky nabízí vývojářům jednoduchou práci se strukturovanými daty (většinou je použita nějaká forma asociativního pole), dynamicky typované proměnné (viz druhá kapitola, ve které se touto problematikou budeme zabývat), automatickou správu paměti (garbage collector) a mnohé další vysokoúrovňové techniky zjednodušující a zrychlující vývoj. Jazyk Lua má navíc velmi jednoduchou – a pro mnoho vývojářů důvěrně známou – syntaxi inspirovanou Modulou a Pascalem, zatímco sémantika jazyka se v mnohém podobá spíše moderním verzím JavaScriptu. Navíc je přímo v jazyku zabudovaná podpora pro paralelní programování, což je stále oblast, kterou mnohé výše zmíněné jazyky prozatím nepodporují.

Obrázek 1: Programovací jazyk Lua byl použit již několika softwarovými firmami (Lucasarts, Biowar) při tvorbě mnoha her. Základem těchto her (typicky se jedná o grafický engine) je program napsaný v céčku či C++, v jazyku Lua je pak naprogramována samotná logika hry, chování počítačem řízených protihráčů (AI), scénář celé hry apod.

Většina programátorů si jazyk Lua oblíbila právě kvůli jeho syntaxi, která zbytečně nepřináší žádné nové prvky (snad jen zápis relačního operátoru nerovnosti pomocí ~= je přinejmenším podivný a neobvyklý). Naopak se snaží programátorům ulehčit život, například možností zápisu vícenásobného přiřazení, přístupu k položkám asociativního pole jak pomocí „tečkové“ notace, tak i s využitím hranatých závorek apod. Jednoduše použitelná syntaxe a současně i velká vyjadřovací schopnost jazyka Lua by však pravděpodobně nedostačovala pro jeho masovější rozšíření. Důvodem, proč jsou některé hry, například Escape from Monkey Island, Grim Fandango, Fish Fillets, Neverwinter Nights či MDK2 z menší či větší části naprogramované právě v Lua, spočívá v tom, že kombinace nízkoúrovňového a skriptovacího jazyka umožňuje soustředit se při vývoji na podstatné věci – herní engine vytvořit co nejefektivnější s využitím všech možností nízkoúrovňového jazyka a naopak herní scénář a logiku hry naskriptovat s co největším urychlením cyklu oprava–překlad–spuštění.

Obrázek 2: Jednání všech NPC (non-playable character, postava řízená počítačem) je v této hře řízeno skriptem napsaným v jazyce Lua.

V mnoha případech se také využívá další užitečné vlastnosti jazyka Lua – celý překladač i interpretr vygenerovaného bajtkódu (popř. pouze interpretr – všechny pojmy jsou vysvětleny dále) je možné velmi snadno zabudovat do jiné aplikace, přičemž se výsledná velikost spustitelného souboru této aplikace zvětší o cca 70 kB (popř. lze volat dynamickou knihovnu o řádově stejné velikosti), což není mnoho, když si uvědomíme, že dostáváme k dispozici plnohodnotný vysokoúrovňový programovací jazyk (ostatně Lua se díky své malé velikosti používá i pro pouhé zpracování konfiguračních souborů, které díky tomu mohou obsahovat různé konstanty, výrazy atd.). Mnozí programátoři, mezi jinými i John Walker (jeden z vývojářů AutoCADu) se netají tím, že právě zabudování programovacího (skriptovacího) jazyka do jejich aplikací mělo velký význam pro jejich úspěch, protože to umožnilo mnoha dalším vývojářům rozšiřovat funkčnost původní aplikace a tím zvýšit její atraktivitu pro uživatele. Firefox využívá XUL, v případě AutoCADu se jednalo o AutoLISP a firma Blizzard Entertainment pro skriptování své veleúspěšné online hry World Of Warcraft vsadila právě na jazyk Lua, který je (přes všechnu úctu, kterou k (Auto)LISPu chovám), přeci jen snáze použitelný i pro neprogramátory.

Stručná historie programovacího jazyka Lua

Vývoj jazyka Lua začal v roce 1993 ve chvíli, kdy skupina inženýrů z brazilské ropné firmy PETROBRAS potřebovala zjednodušit svoji práci při ručním zadávání údajů pro aplikaci provádějící různé simulace. Původně se totiž veškerá vstupní data zapisovala do textového souboru ve formě několika sloupců numerických údajů, bez určení, co která hodnota znamená. Pokud se ve vstupním souboru udělala chyba (některý řádek chyběl či přebýval), poznalo se to až po proběhnutí simulace a následném prohlédnutí výsledků. Aby bylo zadávání vstupních dat zjednodušeno a předešlo se chybám, byl navržen velmi jednoduchý jazyk nazvaný DEL (Data Entry Language), pomocí kterého se vstupní hodnoty daly pojmenovat a dokonce i specifikovat omezující podmínky ne ve formě sloupce čísel, ale jednoduchého výrazu:

:e      gasket            "gasket properties"
mat     s                  # material
m       f       0          # factor m
y       f       0          # settlement stress
t       i       1          # facing type

:p
gasket.m>30
gasket.m<3000
gasket.y>335.8
gasket.y<2576.8

Tento jazyk, který ještě neobsahoval řídicí programové konstrukce (podmíněné příkazy a smyčky), protože byl pouze deklarativní, se osvědčil a – jak je u úspěšných produktů zvykem – postupně se začal rozšiřovat i do dalších oddělení firmy PETROBRAS. Záhy přišly požadavky na další funkcionalitu, především možnost tvorby smyček a zápisu podmíněných bloků kódu. Tvůrcům jazyka začalo být jasné, že potřebují vytvořit (či adaptovat) plnohodnotný programovací jazyk. Současně byl v téže firmě vyvíjen i další nástroj nazvaný PGM, který sloužil pro tvorbu sestav. Konfigurace tohoto nástroje se prováděla pomocí jazyka nazvaného SOL (Simple Object Language), který umožňovat tvořit i typové deklarace. Portugalské slovo „sol“ znamená „slunce“. V polovině roku 1993 došlo ke sloučení obou jazyků a výsledkem byla první verze programovacího jazyka nazvaného Lua (toto portugalské slovo znamená „měsíc“, proto je také logem tohoto jazyka symbolický obrázek Měsíce obíhajícího Zemi).

Zajímavé je, že se namísto relativně nákladného vývoje vlastního jazyka nejprve uvažovalo o použití jiných již existujících programovacích jazyků, ale nakonec se přistoupilo k vývoji nového jazyka, především kvůli požadavkům na portabilitu (šestnáctibitový MS DOS, Unix) a snadné použití i pro inženýry a geology, tj. uživatele, kteří znali pasivně pouze některý z programovacích jazyků vyučovaných na tamních vysokých školách (Fortran, Pascal – zde leží například důvod toho, proč středníky za příkazy nejsou v Lua povinné).

Obrázek 3

Licence

Nejdříve se Lua používala pouze pro interní potřeby firmy PETROBRAS, ovšem již po necelém roce došlo k rozšíření mezi větší počet uživatelů. První oficiální verze tohoto jazyka, která vyšla v roce 1994, nesla označení Lua 1.1. Její licence byla poměrně restriktivní – bezplatné bylo pouze použití jazyka v akademické sféře. To samozřejmě bránilo jejímu většímu rozšíření, nehledě na to, že mnohé akademické projekty byly vyvíjeny s tím, že budou v případě úspěchu využity komerčně (a nebylo by tedy vhodné použít pro tyto projekty programovací jazyk, který by přechod na komerční bázi neumožňoval). Z tohoto důvodu byla další verze – Lua 2.1, již vydána jako free software (zde myšleno ve smyslu software zadarmo) a současně i open source, ovšem s vlastní licencí. Teprve verze Lua 5.0 byla vydána pod známou licencí GPL a právě v této podobě můžeme tento programovací jazyk dnes najít v repozitářích a použít jak v komerčních, tak i nekomerčních projektech.

Obrázek 4: Pomocí jazyka Lua je možné psát skripty i pro známou hru BZFlag.

Základní vlastnosti programovacího jazyka Lua

Již v úvodních odstavcích jsme si řekli, že syntaxe programovacího jazyka Lua byla ve svých principech odvozena od Moduly a Pascalu (je to patrné zejména při pohledu na způsob deklarace funkcí, programových smyček, podmínek a programových bloků), ovšem u některých dalších programových konstrukcí se tvůrci inspirovali i dalšími jazyky, například C a C++ (deklarace proměnných v libovolném programovém bloku, zkrácené vyhodnocování logických výrazů, díky kterému lze logickými výrazy v mnoha případech nahradit podmínky) či Pythonu (vícenásobné přiřazení v rámci jednoho přiřazovacího příkazu). Na rozdíl od programovacích jazyků odvozených syntakticky od Céčka se v Lua mnoho konstrukcí zapisuje pomocí klíčových slov (je jich celkem 21), zatímco „céčkové jazyky“ z větší míry využívají speciální znaky typu {, } či &. Typický program napsaný v imperativním stylu vypadá následovně:

-- poznámky začínají dvojicí znaků --:
-- extracted from Programming Pearls, page 110

-- středníky za příkazy nejsou povinné, zde se nepoužívají

-- Deklarace funkce začíná klíčovým slovem function, za nímž následuje
-- jméno funkce a v závorce seznam parametrů (bez udání typu)
function qsort(x,l,u,f)
-- zápis podmínky, povšimněte si, že vlastní výraz není nutné
-- psát do závorek a používá se klíčové slovo then
if l<u then
-- lokální proměnná, opět se nemusí udávat její typ
local m=math.random(u-(l-1))+l-1  -- choose a random pivot in range l..u
-- použití vícenásobného přiřazení umožňuje jednoduché prohození hodnot
-- uložených v asociativním poli
x[l],x[m]=x[m],x[l]               -- swap pivot to first position
local t=x[l]                      -- pivot value
m=l
local i=l+1
-- programová smyčka, platí pro ni podobná pravidla, jako pro výše
-- okomentovanou podmínku
while i<=u do
-- invariant: x[l+1..m] < t <= x[m+1..i-1]
if f(x[i],t) then
m=m+1
x[m],x[i]=x[i],x[m]       -- swap x[i] and x[m]
end
i=i+1
end
x[l],x[m]=x[m],x[l]               -- swap pivot to a valid place
-- x[l+1..m-1] < x[m] <= x[m+1..u]
-- rekurzivní volání (není zapotřebí použít žádnou formu předběžné deklarace)
qsort(x,l,m-1,f)
qsort(x,m+1,u,f)
end
end

Následuje (prozatím bez podrobnějšího vysvětlení všech podrobností) ukázka kódu, ve kterém je použit spíše funkcionální přístup a také jsou zde využity koprogramy umožňující jednoduchým způsobem vytvořit kód, který se může spouštět paralelně. Právě podpora paralelně běžících částí kódu může do budoucna znamenat malý převrat v použití různých programovacích jazyků, protože jen jejich malá část skutečně podporuje paralelní programování:

function generatefib (n)
return coroutine.wrap(function ()
local a,b = 1, 1
while a <= n do
coroutine.yield(a)
a, b = b, a+b
end
end)
end

for i in generatefib(1000) do print(i) end

Z hlediska uživatelů se jedná o jazyk, který zdrojový text nejprve přeloží do takzvaného bajtkódu a teprve tento bajtkód je následně interpretován (bajtkód obsahuje binárně zakódované instrukce virtuálního registrového procesoru). Překlad do bajtkódu je prováděn automaticky v průběhu načítání uživatelského skriptu, přičemž výsledek je ukládán do operační paměti (samotný bajtkód je méně objemný než zdrojový text a taktéž je rychleji zpracovatelný), ovšem v případě potřeby lze překlad vyvolat z příkazové řádky (bez spuštění skriptu) a poté již distribuovat pouze samotný bajtkód, který je přenositelný na různé operační systémy i platformy (od mikrořadičů až po 64bitové vícejádrové procesory). Samotný překlad do bajtkódu je velmi rychlý, uvádí se, že cca 6× rychlejší než v případě Perlu a 8× rychlejší než Python (u malých skriptů však velký rozdíl v rychlosti překladu nezaznamenáme, u rozsáhlejších kódů má pak velký význam i zátěž disku). Zajímavé je, že původní zásobníkový bajtkód (který se dodnes používá u Javy a .NET, v minulosti pak u známého P-Code) tvůrci nahradili bajtkódem, ve kterém se výrazy vyhodnocují nikoli na zásobníku, ale v registrech (podobně je tomu i v případě Parrotu). Výsledkem je rychlejší interpretace (viz další kapitoly).

Obrázek 5: Další screenshot ze hry BZFlag.

Vestavění překladače a interpretru jazyka Lua do aplikací

Jeden z důvodů relativně velké oblíbenosti jazyka Lua mezi vývojáři spočívá v tom, že její překladač i interpretr je vestavitelný do dalších aplikací, což znamená, že do prakticky libovolného programu je možné zabudovat buď plnohodnotný překladač tohoto jazyka, nebo pouze jeho část, která se stará o běh přeloženého bajtkódu (to je i případ některých výše zmíněných her, v nichž není nutné překládat nové zdrojové kódy, ale pouze spouštět bajtkód přeložený přímo výrobcem hry). Samozřejmě se nejedná o unikátní vlastnost, protože i mnoho interpretrů dalších programovacích jazyků lze vestavět do jiných aplikací (v poslední době se stává populární především JavaScript vedle již zavedeného Pythonu či Scheme). Ovšem v případě Lua je její vestavění skutečně snadné – z pohledu programátora (především pokud programuje v céčku či C++), který ve své aplikaci potřebuje použít nějaký skriptovací jazyk, se jedná o pouhých několik řádků kódu, jak si ostatně ukážeme v následující kapitole.

Vložením celého překladače a interpretru jazyka Lua včetně jeho běhového prostředí se zvětší velikost výsledného spustitelného souboru o cca 70 kB, což není nijak velká hodnota, především při porovnání velikostí interpretrů dalších programovacích jazyků. Lua se z tohoto důvodu dokonce používá i na mikrořadičích s poměrně malou operační pamětí a pamětí ROM (v jedné z aplikací využívající Lua byl použit mikrořadič s 64 kB RAM a 256 kB EEPROM). V tomto případě se většinou využívá pouze ta část interpretru, která se stará o běh přeloženého bajtkódu, v některých situacích se také mění základní numerický datový typ na šestnáctibitové či třicetidvoubitové hodnoty namísto hodnot uložených ve formátu plovoucí tečky (viz soubor luaconf.h, především definice LUA_NUMBER). Vestavěný interpretr jazyka Lua řeší také otázku bezpečnosti skriptů, aby se zabránilo šíření makrovirů, které byly tak „populární“ mezi uživateli jednoho kancelářského balíku.

Problém bezpečnosti je řešen především izolací běhového prostředí skriptů od ostatního systému. Pouze přímo programátor aplikace, která má obsahovat překladač a interpretr Lua, může (explicitně zapsaným importem příslušné knihovny) skriptům povolit například možnost práce se soubory, spouštění dalších programů přes volání os.execute() apod. Bez importu těchto knihoven je skriptu povoleno se svým okolím komunikovat pouze pomocí volání zaregistrovaných funkcí. Pro předávání parametrů se navíc používá zvláštní zásobník, ne standardní rámec procesu (na něj se ukládá pouze jeden ukazatel), takže skripty ani nemají možnost manipulovat se zásobníkem procesu pod kterým běží (tím se eliminují útoky typu stack overflow).

Demonstrační příklad – vestavění překladače a interpretru do céčkového programu

V předchozí kapitole jsme si řekli, že překladač i interpretr jazyka Lua je velmi snadné vestavět do vlastních aplikací. Pod tímto odkazem najdete výpis zdrojového kódu programu napsaného v programovacím jazyce C, který má délku cca 6 kB a více než 300 řádků. Z toho pouze na šesti řádcích označených symbolem /* && */ se nachází příkazy nutné pro inicializaci překladače jazyka Lua a spuštění překladu uživatelských skriptů s jejich následnou interpretací (poctivě jsem mezi těchto šest řádků započítal i dva nutné příkazy #include). Na dalších osmi řádcích, konkrétně ve funkci registerLuaFunctions(), je registrace vlastních funkcí, které je možné volat ze skriptů napsaných v Lua – de facto tak došlo k rozšíření tohoto jazyka o další funkce, s nimiž lze do značné míry zacházet stejně jako s funkcemi vytvořenými přímo ve skriptu. Jedná se o funkce, které do Lua přidávají podporu pro takzvanou želví grafiku vykreslovanou do bitmapy o rozlišení 800×800 pixelů, známou především z programovacího jazyka Logo.

Tento program, který je zde představen ve své nejjednodušší podobě, tvoří základ pro skutečnou aplikaci využitelnou při výuce základů programování – některým vyučujícím se totiž zdá Logo poněkud odlišné od ostatních mainstreamových jazyků, proto raději volí jazyk jiný (reálná výuková aplikace se samozřejmě od tohoto programu odlišuje, především je vybavena grafickým uživatelským rozhraním – v představeném programu se pouze spustí skript uvedený jako první parametr a před ukončením programu se vygeneruje bitmapa ve formátu Portable BitMap). S využitím osmice nových funkcí left, right, forward, back, home, clean, penup a pendown je možné již s malými znalostmi samotného jazyka Lua vytvořit poměrně efektní obrázky (před každým obrázkem je uveden i skript použitý pro jeho vytvoření):

-- vymazani obrazovky
clean()

-- posun zelvy tak, aby byl
-- vykresleny obrazek vystredeny
right(135)
penup()
forward(350)
left(135)
pendown()

-- vlastni obrazec
for i=0, 90 do
forward(500)
left(92)
end

function c1()
for i=0, 3 do
forward(280)
left(90)
end
end

function c2()
for i=0, 360/8-1 do
c1()
forward(10)
left(8)
end
end

clean()
right(90)
penup()
forward(80)
left(90)
pendown()
c2()

function c1(step)
for i=0, 3 do
forward(step)
left(90)
end
end

function c2()
step=50
for i=0, 2*360/4 do
c1(step)
left(4)
step=step+1
end
end

clean()
c2()

function c1(step)
penup()
forward(step)
left(90)
pendown()
forward(step)
left(90)
forward(step)
left(90)
penup()
forward(step)
left(90)
end

function c2()
step=0
for i=0, 360 do
c1(step)
left(5)
step=step+0.7
end
end

clean()
c2()

function kruznice(strana)
for i=0, 360 do
forward(strana)
left(1)
end
end

function kvet(deleni)
for i=0, deleni-1 do
kruznice(3)
left(360.0/deleni)
end
end

clean()
kvet(40)

function star(step1, step2)
for i=0, 35 do
for j=0, 6 do
left(2*360/7)
forward(step1)
end
left(step2)
end
end

clean()
star(300,10)

clean()

step=20
for i=0, 501 do
forward(step)
left(91)
step=step+1
end

Základní konstrukce v programovacím jazyku Lua

Základní programové konstrukce v programovacím jazyku Lua

V této kapitole se podrobněji seznámíme se základní syntaxí tohoto jazyka a způsobem zápisu programových konstrukcí, mezi něž se řadí především programové bloky, podmínky a počítané i nepočítané smyčky. V úvodní kapitole jsme si řekli, že tvůrci programovacího jazyka Lua se při návrhu syntaxe inspirovali především syntaxí jazyků Modula, Pascal a částečně i Fortran; způsob zápisu vícenásobného přiřazení je naopak „vypůjčen“ z jiných vysokoúrovňových skriptovacích jazyků. Avšak vzhledem k tomu, že Lua je dynamicky typovaný jazyk, tj. datové typy nejsou přiřazené proměnným, ale jejich hodnotám, tak se i syntaxe (a samozřejmě taktéž sémantika) od těchto staticky typovaných jazyků v několika směrech poměrně zásadním způsobem odlišuje – týká se to zejména způsobu vytváření lokálních i globálních proměnných a funkcí. Interpretr Lua taktéž dokáže (opět díky dynamickému typování) automaticky provádět některé základní konverze mezi datovými typy, což v mnoha případech vede ke značnému zjednodušení a zpřehlednění vytvářených programů.

Před popisem jednotlivých programových konstrukcí si ještě musíme říci, jakým způsobem lze dále uváděné demonstrační příklady spouštět. Po instalaci Lua by se měly vytvořit mj. i soubory lua (překladač i interpret, na MS Windows jde o soubor lua.exe), luac (pouze překladač do bajtkódu, na MS Windows se jmenuje luac.exe), liblua.a (statická knihovna) a liblua_verze.so (popř. na operačním systému Microsoft Windows soubor lua_verze.dll). Pro pouhé spouštění vlastních programů (skriptů) je nejjednodušší využít překladač a současně i interpretr lua (jedná se o spustitelný soubor, typicky umístěný v adresáři /usr/bin). Spuštění vlastního skriptu je snadné:

lua jméno_skriptu_lua

Pokud má skript hromadně (neinteraktivně) zpracovávat textová či binární data, lze samozřejmě použít všechny běžné prostředky příkazové řádky, jakými jsou přesměrování standardního vstupu, výstupu i chybového výstupu, roury atd.

Podporované datové typy

V programovacím jazyku Lua lze používat osm datových typů vypsaných v tabulce níže. V úvodním odstavci jsme si řekli, že Lua patří mezi dynamicky typované jazyky, tj. typy nejsou přiřazené proměnným, ale jejich hodnotám. To mj. také znamená, že se při vytváření proměnných neuvádí jejich typ, překladač použitý datový typ rozpozná přímo z toho, jakým způsobem je hodnota proměnné zapsána (pokud je místo konstantní hodnoty použit výraz či volání funkce, vyhodnocuje se typ výsledku výrazu či výsledku volání funkce). Samotný název datového typu (první sloupec tabulky) tedy není rezervovaným ani klíčovým slovem, naopak některé hodnoty určitých datových typů (především nil, true, false) klíčovými hodnotami jsou. Určitou výjimkou je první datový typ, který je pojmenovaný stejně jako (jediná a v celém programu jedinečná) hodnota tohoto typu: nil. Podrobnosti o tomto užitečném datovém typu si uvedeme v následující kapitole.

Datový typ

Význam

nil

datový typ hodnoty nil, která je v celém programu jedinečná; tato hodnota se definitoricky odlišuje od všech ostatních hodnot (nutná podmínka pro správné porovnávání); některými vlastnostmi se podobá céčkovému NULL či javovskému null

boolean

datový typ pravdivostních hodnot true a false

number

reálné číslo podle IEEE 754 s dvojnásobnou přesností (v céčku či Javě se jedná o typ double); při překladu jazyka je však možné tento typ změnit, například na celá čísla

string

řetězec

function

funkce, buď vytvořená přímo ve skriptu či zaregistrovaná céčková funkce; s funkcemi lze manipulovat podobně jako s dalšími datovými typy, včetně jejich přiřazení do proměnných, uvedení funkce jako parametru jiné funkce atd.

userdata

data vytvořená v hostitelském (céčkovém) programu; v samotném skriptu je jejich použití omezeno (interpretr nemá dostatek informací o struktuře skutečně uložených dat)

thread

vlákno použité pro implementaci koprogramů (coroutine)

table

asociativní pole – velmi flexibilní datový typ, na kterém je založena většina funkcionality jazyka Lua, včetně objektového systému (v asociativním poli mohou být umístěny mj. i funkce)

Proměnné a přiřazení hodnoty do proměnných

V programovacím jazyku Lua je možné, ostatně jako v prakticky každém imperativním jazyku, pracovat s proměnnými (symbolicky pojmenovanými bloky paměti). Proměnné mohou být globální (platné od místa svého vytvoření v celém skriptu) a lokální (platné v daném bloku či podbloku) – viz další kapitola. Proměnná je v tom nejjednodušším případě vytvořena velmi jednoduše – přiřazením hodnoty ke jménu proměnné pomocí jednoduchého či vícenásobného přiřazení, viz následující příklad (globální funkce print dokáže vytisknout několik hodnot, mezi něž vkládá tabelační zarážku; pro složitější formátování je výhodnější použít string.format):

x=10                       -- vytvoreni globalni promenne x s ciselnou hodnotou
print("x=", x)

url="www.root.cz"          -- vytvoreni globalni promenne s retezcovou hodnotou
print("url=", url)

a=nil                      -- vytvoreni globalni promenne s hodnotou nil
print("a=", a)

b=true                     -- vytvoreni globalni promenne s logickou hodnotou
print("b=", b)

c=false                    -- vytvoreni globalni promenne s logickou hodnotou
print("c=", c)

Skript po svém spuštění vypíše následující text:

x=  10
url=    www.root.cz
a=  nil
b=  true
c=  false

Jména proměnných (a také funkcí) mohou obsahovat znaky malé i velké abecedy, podtržítko a číslice, ovšem prvním znakem nesmí být číslice, podobně jako v mnoha dalších programovacích jazycích. To, které znaky náleží do abecedy, závisí na nastavené lokalizaci, ovšem kvůli snadnému přenosu zdrojových kódů je vhodnější se omezit pouze na abecedu anglickou. Lua patří mezi jazyky, ve kterých se ve jménech proměnných berou v úvahu i velikosti jednotlivých znaků (minusky vs verzálky), tj. například pokus, Pokus a POKUS mohou být jména tří odlišných proměnných. Jméno proměnné či funkce se taktéž nesmí shodovat s žádným klíčovým slovem jazyka Lua – viz následující tabulka se seznamem všech abecedně seřazených klíčových (rezervovaných) slov (vzhledem k výše zmíněnému rozlišování velkých a malých písmen je však možné si například vytvořit proměnnou nazvanou Break či NIL, i když to bude pro čtenáře vašeho kódu minimálně matoucí):

Klíčová slova jazyka Lua

1

and

2

break

3

do

4

else

5

elseif

6

end

7

false

8

for

9

function

10

if

11

in

12

local

13

nil

14

not

15

or

16

repeat

17

return

18

then

19

true

20

until

21

while

Při spuštění interpretru je vytvořeno několik globálních objektů (proměnných, konstant a funkcí), které může skript při svém běhu využít. V případě, že se vytvoří globální proměnná se stejným jménem, jaký má takový objekt, nebude již možné tento objekt v další části skriptu použít. V demonstračním příkladu vypsaném níže je ukázáno, jak je možné znepřístupnit funkci print:

funkce=print               -- promennym lze priradit i funkci
print("funkce=", funkce)

print=1                    -- pozor! prepis globalniho objektu (zde funkce)
-- legalni, nicmene zpusobi problemy dale
print("dofile=", print)    -- nyni jiz funkce print neni dostupna

Výstup skriptu (chybová hláška je přesměrována na chybový výstup):

funkce= function: 003D49F0
lua: 1promenne.lua:29: attempt to call global 'print' (a number value)
stack traceback:
1promenne.lua:29: in main chunk
[C]: ?

Z výše uvedeného důvodu je vhodné, abyste se při pojmenování vlastních objektů (proměnných, konstant či funkcí) raději vyhnuli následujícím identifikátorům, jejichž přesný význam si ještě v této knize popíšeme:

Globální objekty

1

_G

2

_VERSION

3

assert

4

collectgarbage

5

dofile

6

error

7

getfenv

8

getmetatable

9

ipairs

10

load

11

loadfile

12

loadstring

13

module

14

next

15

pairs

16

pcall

17

print

18

rawequal

19

rawget

20

rawset

21

require

22

select

23

setfenv

24

setmetatable

25

tonumber

26

tostring

27

type

28

unpack

29

xpcall

Při přiřazení hodnot do proměnných lze použít takzvané vícenásobné přiřazení. Jedná se ve své podstatě jen o syntaktický cukr, který však může vést ke zjednodušení zápisu programu a dokonce i k ušetření paměťového místa v případech, kdy by bylo při absenci vícenásobného přiřazení nutné použít pomocnou proměnnou. Následuje ukázka, jakým způsobem se vícenásobné přiřazení zapisuje, včetně jeho typického použití při prohození obsahu dvou proměnných:

i,j=10,20                  -- vicenasobne prirazeni
print("i=", i)
print("j=", j)

i,j=j,i                    -- prohozeni obsahu dvou promennych
print("i=", i)
print("j=", j)

Výstup skriptu po jeho spuštění:

i=  10
j=  20
i=  20
j=  10

Bloky a jejich význam při ovlivňování viditelnosti proměnných

Programovací jazyk Lua má zaveden koncept takzvaných „bloků“. Jedná se o části kódu zapsané mezi klíčová slova do a end (naproti tomu jazyky odvozené od Céčka používají k prakticky stejnému účelu znaky { a }). Význam bloků spočívá v tom, že se pomocí nich dají omezit viditelnosti proměnných – každá lokální proměnná vytvořená v bloku je viditelná pouze v tomto bloku nebo v blocích vložených. Bloky se v některých případech používají také tehdy, když je nutné doprostřed části kódu vložit příkazy return nebo break. Ve specifikaci jazyka je totiž předepsáno, že se tyto příkazy vždy musí vyskytovat na konci bloku. Pokud však vytvoříme vnitřní blok s jediným příkazem, například do return end či do break end, je tato podmínka splněna (popravdě řečeno se s tímto idiomem příliš často nesetkáme). V blocích je možné vytvářet lokální proměnné, které mohou překrýt viditelnost proměnných z nadřazených bloků. Následuje demonstrační příklad ukazující překrytí viditelnost globální proměnné a proměnné vytvořené v nadřazeném bloku:

x=10                              -- vytvoreni globalni promenne x
print("globalni x=", x)

do                                -- zacatek bloku
local x=20                    -- vytvoreni lokalni promenne x
print("1. lokalni x=", x)
do                            -- zacatek zanoreneho bloku
local x=30                -- vytvoreni lokalni promenne x
print("2. lokalni x=", x)
end                           -- konec zanoreneho bloku
print("1. lokalni x=", x)
end                               -- konec bloku

print("globalni x=", x)

Skript po svém spuštění vytiskne následující text, z něhož je patrné, že uvnitř bloků se používá odlišná lokální proměnná, než v bloku nadřazeném:

globalni x=     10
1. lokalni x=   20
2. lokalni x=   30
1. lokalni x=   20
globalni x=     10

Podmínky

Jazyk Lua obsahuje úplnou programovou konstrukci typu if-then-else, pomocí níž je možné blok příkazů provést pouze při splnění nějaké podmínky (výrazu, který se vyhodnotí na pravdivostní hodnotu true či false). Tuto programovou konstrukci je možné podle potřeb zapsat různým způsobem. V nejjednodušším případě se používá forma obsahující pouze jeden programový blok (takzvanou větev):

if výraz then
programový blok vykonaný při splnění podmínky
end

Jak jsme se již zmínili v předchozím odstavci, je podporována i úplná konstrukce if-then-else s oběma větvemi. Příkazy v první větvi jsou vykonány při splnění podmínky, příkazy ve větvi druhé naopak při jejím nesplnění:

if výraz then
programový blok vykonaný při splnění podmínky
else
programový blok vykonaný při nesplnění podmínky
end

Navíc je možné (jako náhradu za chybějící konstrukci switch) použít více větví, přičemž u každé větve je uvedena vlastní podmínka:

if výraz then
programový blok vykonaný při splnění první podmínky
elseif výraz then
programový blok vykonaný při splnění druhé podmínky
elseif výraz then
programový blok vykonaný při splnění třetí podmínky
else
programový blok vykonaný při nesplnění podmínky
end

Chybějící konstrukce switch se v mnoha případech nahrazuje také pomocí asociativních polí, což je technika, kterou si ukážeme v některé z dalších kapitol (ostatně právě absence implementace asociativních polí a funkcí jakožto plnohodnotných datových typů v mnoha jiných jazycích programátory nutí switch používat, i když mnohdy velmi neefektivně).

V podmíněném bloku se samozřejmě může nacházet i další podmínka. Důležité v tomto případě je, jakým způsobem překladač určí, kterému bloku náleží příslušné větve elseif a else. Na rozdíl od Pascalu, ve kterém byla zavedena – pro začátečníky poněkud matoucí – pravidla, je v Lua vše jednoduché: každý blok (a celý podmíněný příkaz i se všemi větvemi blokem je) musí být ukončen klíčovým slovem end, podle čehož se také určuje, kterému podmíněnému příkazu větve elseif a else náleží. Následuje ukázka získaná z reálného skriptu, jednotlivé podmíněné příkazy jsou zvýrazněny odsazením (které sice není součástí syntaxe jako v Pythonu, ale je samozřejmě vhodné ho kvůli zvýšení čitelnosti používat):

if event=="call" then
level=level+1
else
level=level-1
if level<0 then
level=0
end
end
if t.what=="main" then
if event=="call" then
io.write("begin ", t.short_src)
else
io.write("end ", t.short_src)
end
elseif t.what=="Lua" then
io.write(event, " ", t.name or "(Lua)"," <",t.linedefined,":",t.short_src,">")
else
io.write(event, " ", t.name or "(C)"," [",t.what,"] ")
end

Programová smyčka typu while

Části kódu, které se mají při běhu programu opakovat, je možné zapsat buď pomocí rekurze (tu si ukážeme při popisu funkcí), nebo pomocí programových smyček (loop). Základním typem smyčky je smyčka s podmínkou na začátku typu while, jejíž způsob zápisu v jazyce Lua je následující:

while podmínka do
programový blok vykonaný při splnění první podmínky
end

Smyčka je vykonávána tak dlouho, dokud je podmínka vyhodnocena jako pravdivá (má pravdivostní hodnotu true). V případě, že je podmínka již na začátku vyhodnocena jako nepravdivá, neproběhne smyčka ani jednou (na to je zapotřebí dávat pozor při zápisu algoritmu – někdy se může zapomenout na to, že je zapotřebí proměnné vystupující v podmínce vhodně inicializovat již před vstupem do smyčky). Naopak pokud se podmínka po vstupu do smyčky nemění, bude se program nacházet v oblíbené nekonečné smyčce (infinite loop). Následuje příklad použití smyčky typu while při výpočtu Fibonacciho posloupnosti od jedničky až po mezní hodnotu (zapsanou do proměnné n). Povšimněte si dvojího použití vícenásobného přiřazení, které celý program zjednodušuje:

-- vypocet Fibonacciho posloupnosti az do mezni hodnoty
n = 1000
a, b = 1, 1
while a <= n do
print(a)
a, b = b, a+b
end

Výstup programu pro zadané n=1000:

1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987

Programová smyčka typu repeat–until

Méně často používaným typem smyčky je smyčka typu repeat-until. Opět se jedná o smyčku s podmínkou, tentokrát je však podmínka zapsána (a při běhu programu vyhodnocována) až na samotném konci smyčky. To znamená, že tato smyčka proběhne vždy minimálně jedenkrát, protože teprve až po prvním proběhnutí smyčky se podmínka může vyhodnotit. Na rozdíl od výše popsané smyčky typu while je u smyčky repeat-until smysl podmínky otočen – další běh smyčky je proveden v případě, že je podmínka vyhodnocena na pravdivostní hodnotu false (v případě nejasnosti můžeme použít mnemotechnickou pomůcku – vždy když je podmínka splněna, běh programu pokračuje od zápisu podmínky směrem „dolů“, což v případě smyčky while znamená do těla smyčky a u smyčky repeat-until za smyčku). Zajímavé a užitečné je, že programový blok představovaný tímto typem smyčky, končí až za zápisem podmínky, což mj. znamená, že v podmínce se mohou vyskytovat lokální proměnné definované uvnitř těla smyčky – je to sice poněkud netypická vlastnost, která však může celý program zjednodušit, neboť se pomocné proměnné vytváří pouze v bloku, ve kterém jsou skutečně zapotřebí. Výše uvedený program pro výpočet Fibonacciho posloupnosti lze přepsat pomocí smyčky repeat-until následovně:

-- vypocet Fibonacciho posloupnosti az do mezni hodnoty
n = 1000
a, b = 1, 1
repeat
print(a)
a, b = b, a+b
until a > n

Pozor: nejedná se o zcela identické programy, jejich výsledek se bude pro určité hodnoty n lišit. Zkuste si sami – bez pomoci překladače – zjistit, pro jaké n bude chování obou programů odlišné.

Dvojí podoba programové smyčky typu for

Posledním typem smyčky, kterou je jazyk Lua v současné verzi vybaven, je počítaná smyčka typu for a smyčka foreach používající iterátory. Syntaxe zápisu tohoto typu smyčky je různá podle toho, jakého výsledku potřebujeme s využitím smyčky dosáhnout. Nejjednodušší formu má tato smyčka v případě, že potřebujeme do zvolené proměnné postupně dosazovat celočíselné hodnoty od x1 do x2 (hodnoty se zvyšují po jedničce):

for prom=x1, x2 do
blok příkazů umístěný v těle smyčky
end

V případě, že se má krok, tj. hodnota, o kterou se má řídicí proměnná smyčky zvýšit či snížit, lišit od jedničky, používá se rozšířená podoba počítané smyčky for:

for prom=x1, x2, krok do
blok příkazů umístěný v těle smyčky
end

Počítanou smyčku typu for jsme použili i v první kapitole při vykreslování obrazců pomocí želví grafiky:

function star(step1, step2)
for i=0, 35 do
for j=0, 6 do
left(2*360/7)
forward(step1)
end
left(step2)
end
end

clean()
star(300,10)

V Lua existuje i smyčka typu foreach, kterou je možné použít například při procházení všemi prvky asociativního pole (obecně při vyhodnocování s využitím iterátoru). Způsob zápisu tohoto typu smyčky, který si blíže popíšeme až v následující kapitole, je následující:

for namelist in explist do
blok příkazů umístěný v těle smyčky
end

Následuje příklad použití tohoto typu smyčky při výpisu čísla a jména jednotlivých měsíců v roce (zde se využívá faktu, že asociativní pole lze inicializovat i bez uvedení klíčů – v tomto případě jsou použita přirozená čísla):

months={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}

for poradi,month in pairs(months) do
print(poradi, month)
end

Výstup programu:

1       Jan
2       Feb
3       Mar
4       Apr
5       May
6       Jun
7       Jul
8       Aug
9       Sep
10      Oct
11      Nov
12      Dec

Operátory a asociativní pole v jazyku Lua

Aritmetické operátory

Programovací jazyk Lua obsahuje celkem sedm základních aritmetických operátorů aplikovatelných na číselné hodnoty či proměnné (viz předchozí kapitola), popř. na řetězce převedené automaticky na čísla. Jedná se o součet, rozdíl, součin, podíl (neceločíselný), dělení modulo (zbytek po dělení), umocnění a v neposlední řadě též unární minus (změna znaménka); všechny zmíněné operátory jsou vypsány v tabulce pod odstavcem. Při práci s číselnými hodnotami si musíme dát pozor na některé problémy, které mohou vznikat na základě zvolené reprezentace čísel. Pokud používáme standardně přeložený interpretr jazyka Lua, jsme v přesnosti i rozsahu využitelných (reprezentovatelných) hodnot omezeni 64bitovým datovým typem double definovaným v normě IEEE 754. Nejvyšší reprezentovatelná hodnota dosahuje 1,8×10^308, nejmenší možná nenulová kladná hodnota je rovna 2,2×10-308. Pro exponent je vyhrazeno 11 bitů a pro mantisu zbývajících 53 bitů (přesněji 52 bitů plus bit znaménkový), což pro nás jako programátory znamená, že pouze 53bitová celá čísla lze v tomto formátu reprezentovat zcela přesně, po překročení tohoto rozsahu již dochází k zaokrouhlení či ořezání nejnižších bitů zpracovávané hodnoty (jinými slovy – typ long se obecně na typ double převede s chybou, přijdeme totiž o informace uložené v posledních 11 bitech s nejnižší váhou).

Symbol operátoru

Arita

Popis

+

binární

součet

-

binární

rozdíl

*

binární

součin

/

binární

podíl (reálná čísla)

%

binární

dělení modulo

^

binární

umocňování

-

unární

unární minus

Následuje ukázka krátkého demonstračního programu (skriptu) napsaného v jazyce Lua, na kterém je vysvětlen způsob použití všech výše popsaných aritmetických operátorů při výpočtu součtu, rozdílu, součinu, podílu i zbytku po dělení dvou konstant, stejně jako umocnění první konstanty konstantou druhou (xy). Pomocí operátoru umocňování lze vypočítat i druhou odmocninu jakékoli nezáporné hodnoty bez nutnosti použití knihovní funkce math.sqrt() (základní knihovní funkce si popíšeme v některé z dalších kapitol). V demonstračním příkladu je vypočtena druhá odmocnina hodnoty 2. Program ve své závěrečné části vypíše dvě tabulky – první tabulka obsahuje celé kladné mocniny dvojky (tyto hodnoty programátoři většinou znají nazpaměť :-) a následně i záporné mocniny dvojky (tj. vlastně hodnoty 1/(2n), protože záporný exponent odpovídá výpočtu převrácené hodnoty hodnoty s kladným exponentem). Zdrojový kód demonstračního příkladu je velmi jednoduchý:

-- priklad na pouziti aritmetickych operatoru

-- promenne, se kterymi se budou vypocty provadet
a=10
b=20

-- vypiseme hodnoty promennych
print("a=", a)
print("b=", b)

-- vysledky aritmetickych vyrazu
print("a+b=", a+b)
print("a-b=", a-b)
print("a*b=", a*b)
print("a/b=", a/b) -- vysledkem teto operace je realne cislo
print("a%b=", a%b)
print("a^b=", a^b)

-- pomoci operatoru umocnovani lze vypocitat i odmocninu
print("sqrt(2)=", 2^(0.5))

-- vypocet celych kladnych mocnin zakladu 2
print()
print("mocniny cisla 2")

print("x", "2^x")
for x=0,16 do
print(x, 2^x)
end

-- vypocet celych zapornych mocnin zakladu 2
print()
print("x", "2^(-x)")
for x=0,16 do
print(x, 2^(-x))
end

-- finito

Výše uvedený program po svém spuštění vygeneruje následující výstup:

a=        10
b=        20
a+b=      30
a-b=      -10
a*b=      200
a/b=      0.5
a%b=      10
a^b=      1e+020
sqrt(2)=  1.4142135623731

mocniny cisla 2
x         2^x
0         1
1         2
2         4
3         8
4         16
5         32
6         64
7         128
8         256
9         512
10        1024
11        2048
12        4096
13        8192
14        16384
15        32768
16        65536

x         2^(-x)
0         1
1         0.5
2         0.25
3         0.125
4         0.0625
5         0.03125
6         0.015625
7         0.0078125
8         0.00390625
9         0.001953125
10        0.0009765625
11        0.00048828125
12        0.000244140625
13        0.0001220703125
14        6.103515625e-005
15        3.0517578125e-005
16        1.52587890625e-005

Při použití operátoru umocnění si musíme dát pozor na jeho asociativitu, která je opačná, než u ostatních operátorů (umocnění je automaticky „uzávorkováno“ zprava). V následujícím demonstračním příkladu je ukázáno, jakým způsobem je vypočten výraz a^b^c – výsledek je shodný, jako při použití výrazu a^(b^c), tj. implicitní závorky jsou vloženy zprava a nikoli zleva, tak, jako tomu je u většiny ostatních binárních operátorů:

-- test asociativity operatoru umocnovani

-- promenne, ktere v programu pouzijeme
a=2
b=3
c=4

-- vypis hodnot promennych
print("a=", a)
print("b=", b)
print("c=", c)

-- vypis vysledku vyrazu s operatorem umocnovani
print("a^b^c=", a^b^c)
print("(a^b)^c=", (a^b)^c)
print("a^(b^c)=", a^(b^c))

-- finito

Výstup z výše uvedeného programu po jeho spuštění:

a=        2
b=        3
c=        4
a^b^c=    2.4178516392293e+024
(a^b)^c=  4096
a^(b^c)=  2.4178516392293e+024

Následuje nepatrně složitější příklad, pomocí něhož lze vypočítat několik prvních číslic hodnoty π pomocí opakovaného výpočtu aritmetického a geometrického průměru dvou proměnných, jejichž počáteční hodnota je nastavena na 0, resp. 1/2. Převrácená hodnota obou proměnných se postupně přibližuje k hodnotě čísla π (ve skutečnosti se nejedná o nijak rychlý ani efektivní algoritmus, je však velmi jednoduchý):

-- prakticke pouziti aritmetickych operatoru
-- pri iterativnim vypoctu cisla Pi

-- hodnoty vstupujici do vypoctu
a=0
b=1/2
for x=1, 30 do
-- aritmeticky prumer
a=(a+b)/2
-- geometricky prumer
b=(a*b)^(1/2)
-- k cislu Pi se priblizujeme zespodu od
-- dvojky (1/b) a zvrchu od ctverky (1/a)
print(x, 1/a, 1/b)
end

-- finito

Výstup programu:

1       4               2.8284271247462
2       3.3137084989848 3.0614674589207
3       3.1825978780745 3.1214451522581
4       3.1517249074293 3.1365484905459
5       3.1441183852459 3.1403311569548
6       3.1422236299425 3.1412772509328
7       3.141750369169  3.1415138011443
8       3.1416320807032 3.1415729403671
9       3.1416025102568 3.1415877252772
10      3.1415951177496 3.1415914215112
11      3.1415932696293 3.1415923455701
12      3.1415928075996 3.1415925765849
13      3.1415926920923 3.1415926343386
14      3.1415926632154 3.141592648777
15      3.1415926559962 3.1415926523866
16      3.1415926541914 3.141592653289
17      3.1415926537402 3.1415926535146
18      3.1415926536274 3.141592653571
19      3.1415926535992 3.1415926535851
20      3.1415926535921 3.1415926535886
21      3.1415926535904 3.1415926535895
22      3.1415926535899 3.1415926535897
23      3.1415926535898 3.1415926535898
24      3.1415926535898 3.1415926535898
25      3.1415926535898 3.1415926535898
26      3.1415926535898 3.1415926535898
27      3.1415926535898 3.1415926535898
28      3.1415926535898 3.1415926535898
29      3.1415926535898 3.1415926535898
30      3.1415926535898 3.1415926535898

Logické operátory

Při zápisu logických výrazů v podmínkách a programových smyčkách se často využívají logické operátory. V programovacím jazyku Lua nejsou tyto operátory reprezentovány speciálními symboly (tak jako u jazyků odvozených od Céčka), ale klíčovými slovy and (logický součin), or (logický součet) a not (logická negace) – viz tabulku vypsanou pod odstavcem. Jako operandy logických operátorů mohou vystupovat jak logické proměnné a konstanty (true, false), tak i hodnoty dalších datových typů, přičemž pouze hodnoty false a nil jsou považovány za „nepravdu“, všechny ostatní hodnoty jsou definitoricky pravdivé (pozor – některé jiné jazyky považují za hodnotu „nepravda“ i prázdné řetězce, v Lua to však neplatí, jak si ostatně ukážeme na demonstračním příkladu). Následuje tabulka s trojicí podporovaných logických operátorů:

Symbol operátoru

Arita

Popis

and

binární

logický součin

or

binární

logický součet

not

unární

logická negace

Od mnoha dalších programovacích jazyků se Lua odlišuje v tom, že výsledkem vyhodnocení logických operátorů nemusí být nutně jedna z hodnot true či false, ale přímo jeden z operandů – pokud je hodnota prvního operandu u operátoru and rovna false či nil, je tato hodnota přímo vrácena bez její další konverze (pokud se jedná o výraz, je samozřejmě následně vyčíslen). V opačném případě je vrácena hodnota druhého operandu bez jeho převodu na logickou hodnotu. Logický operátor or pracuje podobně – pokud je jeho první operand rozdílný od false či nil, vrátí přímo jeho hodnotu, v opačném případě vrátí druhý operand (mnoho podmínek lze díky tomuto chování nahradit pouze logickým výrazem). Oba zmíněné logické operátory vyhodnocují druhý operand pouze v případě nutnosti, tj. pokud výsledek není známý už z vyhodnocení operandu prvního (jedná se o takzvané zkrácené vyhodnocování neboli short cut evaluation). Tato vlastnost vede k častému používání idiomu typu if t~=nil and t.currentline>=0 then, který by nemohl být použit u jazyků, které zkrácené vyhodnocování logických výrazů neprovádějí.

Následuje příklad, který některé vlastnosti logických operátorů přiblíží:

-- logicke operatory
print()
print("logicky operator and")
print("false and false  =", false and false)
print("true  and false  =", true  and false)
print("false and true   =", false and true )
print("true  and true   =", true  and true )
print("true  and 'ahoj' =", true  and 'ahoj')
print("true  and nil    =", true  and nil)
print("false and 'ahoj' =", false and 'ahoj')
print("nil   and 'ahoj' =", nil   and 'ahoj')
print("'ahoj' and nil   =", 'ahoj'and nil)

print()
print("logicky operator or")
print("false or false  =", false or false)
print("true  or false  =", true  or false)
print("false or true   =", false or true )
print("true  or true   =", true  or true )
print("true  or 'ahoj' =", true  or 'ahoj')
print("true  or nil    =", true  or nil)
print("false or 'ahoj' =", false or 'ahoj')
print("nil   or 'ahoj' =", nil   or 'ahoj')
print("'ahoj' or nil   =", 'ahoj'or nil)

print()
print("logicky operator not")
print("not false=", not false)
print("not true =", not true)
print("not nil  =", not nil)
print("not ''   =", not '') -- prazdny retezec je "pravdivy"
print("not not ''=", not not '')

-- finito

Výstup příkladu po jeho spuštění:

logicky operator and
false and false  =      false
true  and false  =      false
false and true   =      false
true  and true   =      true
true  and 'ahoj' =      ahoj
true  and nil    =      nil
false and 'ahoj' =      false
nil   and 'ahoj' =      nil
'ahoj' and nil   =      nil

logicky operator or
false or false  =       false
true  or false  =       true
false or true   =       true
true  or true   =       true
true  or 'ahoj' =       true
true  or nil    =       true
false or 'ahoj' =       ahoj
nil   or 'ahoj' =       ahoj
'ahoj' or nil   =       ahoj

logicky operator not
not false=      true
not true =      false
not nil  =      true
not ''   =      false
not not ''=     true

Relační operátory

Programovací jazyk Lua obsahuje obvyklou sadu šesti relačních operátorů vracejících pravdivostní hodnotu true či false, které jsou vypsány v tabulce umístěné pod tímto odstavcem. Tyto operátory mohou sloužit jak pro vzájemné porovnání číselných hodnot, tak i řetězců (lexikografické porovnání) a v některých případech i dalších datových typů (například u tabulek jsou porovnávány reference, nikoli jejich obsah). Důležitý je především operátor rovnosti == a jeho negace ~=. Tento operátor nejprve porovnává typ obou operandů a pokud se typy liší, vrací se automaticky hodnota false (nedojde tedy k automatické konverzi!). Teprve v případě, že jsou typy obou operandů shodné, dojde k jejich skutečnému porovnání. V praxi to například znamená, že výsledek porovnání „10“==10 bude vždy false, i když v jiných případech (aritmetické operátory atd.) by Lua v případě potřeby provedla konverzi řetězce na číselnou hodnotu či naopak konverzi číselné hodnoty na řetězec.

Symbol operátoru

Arita

Popis

==

binární

rovnost

~=

binární

nerovnost

<

binární

menší než

>

binární

větší než

<=

binární

menší nebo rovno

>=

binární

větší nebo rovno

Následuje demonstrační příklad, ve kterém jsou relační operátory použity pro porovnání řetězců i číselných hodnot:

-- lexikograficke porovnani retezcu
print("'a'<'b': ", "a" < "b")
print("'a'<='b': ", "a" <= "b")
print("'aa' < 'ab': ", "aa" < "ab")
print("'aaaaaa' ~= 'aaaaab'", "aaaaaa" ~= "aaaaab")

-- pozor, operator == nejprve porovnava
-- typy operandu a az po jejich shode
-- vlastni hodnoty
print('"a"=="a":',   "a"=="a")
print('10==10:',     10==10)
print('"10"=="10":', "10"=="10")
print('"10"==10:',   "10"==10) -- !!!!

-- finito

Výstup programu:

'a'<'b':          true
'a'<='b':         true
'aa' < 'ab':      true
'aaaaaa' ~= 'aaaaab' true
"a"=="a":            true
10==10:              true
"10"=="10":          true
"10"==10:            false

Spojení řetězců a operátor vracející délku struktury

Dva poslední operátory, se kterými se dnes seznámíme, slouží ke spojení dvou řetězců a zjištění délky řetězce či jiné datové struktury (typicky asociativního pole). Řetězce je možné spojit pomocí operátoru zapisovaného dvojicí teček ... Tento (na první pohled možná poněkud nadbytečný) operátor byl do jazyka přidán zejména kvůli automatickým typovým konverzím, protože výraz typu „10“+„20“ je vyhodnocen – nezávisle na tom, že oba operandy jsou řetězce – na hodnotu 30 v důsledku výše zmíněné automatické typové konverze. Unární operátor # vrací délku řetězce nebo jiné datové struktury (pole). Například příkaz print(#„Hello world!“) vypíše na standardní výstup číslo 12. Vypočtená délka se liší podle toho, na jaký typ dat je operátor # aplikován. U řetězců se vrátí počet bajtů nutných pro jejich uložení do operační paměti (ve skutečnosti je nutné kromě vlastního řetězce do paměti uložit i jeho délku, ta se však do výsledku nezapočítá) u asociativního pole s celočíselnými (po sobě jdoucími) indexy pak počet jeho prvků – viz další kapitoly.

Symbol operátoru

Arita

Popis

..

binární

spojení dvou řetězců nebo číslic převedených na řetězec

#

unární

délka řetězce nebo velikost asociativního pole

Priority operátorů

Jednotlivé operátory popsané v předchozích kapitolách je možné rozdělit podle jejich priority do osmi skupin. Priorita, pomocí níž se určuje, které části výrazu se vyhodnotí nejdříve, se určuje již při překladu programu. Operátorem s nejvyšší prioritou je umocňování ^, naopak nejnižší prioritu má logický operátor or. Při pohledu na níže uvedenou tabulku zjistíme, že priority operátorů jsou zvoleny velmi rozumně – odpovídají jak matematickým pravidlům (umocňování má nejvyšší prioritu, následuje násobení a dělení, naopak sčítání a odčítání jsou aritmetické operátory s nejnižší prioritou), tak i požadavkům z programátorské praxe, aby se zabránilo nutnosti používání zbytečných závorek například při psaní příkazů typu:
if x>10 and x<20 or x>110 and x<120 then … end.

Priorita

Operátory

1 (nejvyšší)

^

2

not # unární minus (-)

3

* / %

4

+ -

5

..

6

== ~= < > <= >=

7

and

8 (nejnižší)

or

Asociativní pole

V předchozí kapitole jsme si popsali jednotlivé datové typy, se kterými můžeme v programovacím jazyku Lua pracovat. Mezi podporovanými datovými typy se nachází i asociativní pole, někdy také nazývané hashmapa. Jedná se o datovou strukturu, v níž jsou uloženy dvojice klíč–hodnota, kde klíčem může být libovolný typ kromě typu nil, hodnota může být libovolná (může se jednat i o další pole, funkci atd.). V případě, že za klíče jsou zvolena přirozená čísla, jsou asociativní pole ze sémantického hlediska rovnocenná klasicky chápaným indexovaným polím. Tento datový typ tak představuje základ jak pro tvorbu indexovaných polí, tak i seznamů, zásobníků, front, stromů, struktur typu záznam (jako klíče jsou v tomto případě použity řetězce) a dokonce i objektů (jako hodnoty uložené v asociativním poli lze totiž použít i funkce, které tak vystupují v roli metod). V následujících kapitolách si ukážeme základy práce s asociativním polem, podrobnější (a zajímavější) způsoby práce budou vysvětleny v navazující kapitole.

Vytvoření asociativního pole

Existuje několik různých způsobů, pomocí nichž je možné vytvořit asociativní pole. Nejjednodušší způsob spočívá v konstrukci pole jediným příkazem, v němž jsou vypsány všechny ukládané dvojice klíč–hodnota:

pole={klic1="hodnota1", klic2="hodnota2"}

Popř. je povolené za poslední dvojicí uvést čárku navíc. Tato čárka se může objevit například v automaticky generovaném kódu (mimochodem například populární formát JSON založený na JavaScriptu tuto čárku nepovoluje, což zbytečně komplikuje všechny generátory dat). Verze s nadbytečnou čárkou vypadá velmi podobně jako předchozí úryvek kódu:

pole={klic1="hodnota1", klic2="hodnota2",}

V případě, že se má vytvořit „klasické“ pole s celočíselnými indexy, je možné specifikaci klíčů vynechat. V tomto případě překladač sám potřebné indexy doplní; ovšem musíme si dát pozor na to, že v Lua indexy začínají od jedničky, nikoli od nuly, jak je tomu v některých dalších programovacích jazycích! (důvod hledejme v historii tohoto jazyka, který byl určen zejména pro laiky a nikoli profesionální programátory). Alternativní konstrukce pole s automaticky generovanými klíči vypadá následovně:

pole={"hodnota1", "hodnota2", "hodnota3"}

Oba dva způsoby je možné navzájem promíchat. V tomto případě se u prvků neobsahujících klíč budou automaticky dopočítávat indexy, a u prvků, jež klíč obsahují, se samozřejmě klíč do asociativního pole uloží v nezměněné podobě. Důležité je, že i explicitním zadáním celočíselného klíče se nemění hodnota interního čítače, pomocí kterého překladač asociativní pole konstruuje (jinými slovy – nemůžeme překladači jednoduše nařídit, aby například začal automaticky číslování od hodnoty 42). Vše možná osvětlí následující příklad, ve kterém se asociativní pole konstruuje s prvky, z nichž u některých je klíč uveden explicitně a u dalších se ponechává generování klíče (tj. v tomto případě indexu) na překladači. O výpis celého pole se stará smyčka typu foreach spolu s funkcí pairs, pomocí níž je možné iterovat celým asociativním polem:

pole={klic1="hodnota1", "hodnota2", klic2="hodnota3", "hodnota4"}

for klic,hodnota in pairs(pole) do
print(klic, hodnota)
end

Výstup programu:

1       hodnota2
2       hodnota4
klic1   hodnota1
klic2   hodnota3

Vzhledem k tomu, že klíč je v poli vždy jedinečný (může existovat maximálně jeden prvek s daným klíčem), může nastat situace, že se při konstrukci pole prvky navzájem přepíšou, jako tomu je v následujícím případě, kde je původní hodnota „hodnota1“ přepsána novým řetězcem „hodnotax“:

pole={klic1="hodnota1", "hodnota2", klic2="hodnota3", "hodnota4", klic1="hodnotax", }

Přístup k prvkům asociativního pole

Po zkonstruování asociativního pole je možné přistupovat k jeho jednotlivým prvkům pomocí zápisu, který je prakticky shodný ve většině současných programovacích jazyků: identifikátor_pole[klíč]. Za identifikátor (jméno) asociativního pole se do hranatých závorek zapíše klíč, což může být libovolná hodnota (typicky číslo či řetězec), proměnná či výraz. V případě, že se v asociativním poli nachází prvek s daným klíčem, je hodnota tohoto prvku vrácena. Pokud prvek naopak nalezen není, vrátí se hodnota nil. Kromě tohoto způsobu zápisu nabízí Lua i alternativní způsob (syntaktický cukr), který se často používá v případech, kdy jsou asociativní pole použita ve funkci záznamu (record, struct) či objektu. Tento způsob se zapisuje následovně: identifikátor_pole.klíč, tj. klíč zde není uveden v hranatých závorkách, ale za identifikátorem pole, od něhož je oddělen tečkou. Příklad použití:

pole={klic1="hodnota1", "hodnota2", klic2="hodnota3", "hodnota4"}

print(pole["klic1"])
print(pole["klic2"])
print(pole["klic3"]) -- neexistujici prvek, vypise se "nil"
print(pole.klic1)
print(pole.klic2)
print(pole.klic3)    -- neexistujici prvek, vypise se "nil"

Po spuštění programu se na standardní výstup vypíše:

hodnota1
hodnota3
nil
hodnota1
hodnota3
nil

Funkce v programovacím jazyku Lua

Definice funkcí

Ve druhé kapitole jsme se seznámili se všemi datovými typy, které je možné v programovacím jazyku Lua použít. Jeden z osmi podporovaných datových typů představují i funkce – samotný fakt, že funkce patří mezi datové typy je z programátorského hlediska velmi významný, neboť je možné, jak si ukážeme v navazujících kapitolách i další kapitole, funkce přiřazovat proměnným, předávat funkce jako parametry jiným funkcím, ukládat funkce do asociativních polí atd. Ukažme si nyní, jakým způsobem se funkce většinou definují a pojmenovávají (jak uvidíme dále, definice pojmenovaných funkcí je pouze „syntaktickým cukrem“ zavedeným do syntaxe proto, aby se Lua snadněji používala programátorům zvyklým na imperativní jazyky). Definice pojmenované funkce začíná klíčovým slovem function, za nímž následuje název funkce následovaný kulatými závorkami, ve kterých mohou být uvedeny parametry funkce. Za těmito závorkami se nachází tělo funkce, což je ze syntaktického hlediska programový blok (viz druhá kapitola, část 4) ukončený klíčovým slovem end.

Funkce může pomocí příkazu return vracet libovolné množství (návratových) hodnot. V případě, že příkaz return není před koncem těla funkce (či uvnitř programových smyček popř. podmínek) použit, nevrací funkce žádné hodnoty, tj. ze sémantického hlediska se jedná o proceduru. Uvnitř těla funkce je možné, ostatně jako v kterémkoli jiném programovém bloku, vytvářet lokální proměnné, na tomtéž místě lze používat i proměnné globální (i když se to nedoporučuje, protože se tím narušuje obecná představa o funkcích jakožto objektech, které získávají veškeré informace o svém okolí pomocí parametrů a naopak okolí může získat informace z funkce jen pomocí návratové hodnoty či několika hodnot). Následuje příklad, ve kterém jsou vytvořeny dvě funkce nazvané printHello a printWorld. První z těchto funkcí používá ve svém těle lokální proměnnou nazvanou helloStr, druhá funkce se naopak odkazuje na globální proměnnou worldStr. Povšimněte si, že změna globální proměnné ovlivní i chování funkce, tj. vytištěný řetězec:

-- Prvni demonstracni priklad:
-- definice vlastnich funkci

-- funkce obsahujici lokalni promennou
function printHello()
local helloStr = "Hello"
print(helloStr)
end

worldStr="World"

-- funkce vyuzivajici globalni promennou
function printWorld()
-- ke globalni promenne je pripojena
-- retezcova konstanta (literal)
print(worldStr .. "!")
end

-- volani obou funkci
printHello()
printWorld()

-- zmena globalni promenne
-- se projevi i ve volane funkci
worldStr = 42
printWorld()

-- finito

Text vypsaný na standardní výstup po spuštění prvního demonstračního příkladu:

Hello
World!
42!

Funkce jako datový typ

V předchozí kapitole jsme si řekli, že definice funkce ve stylu function jméno() tělo funkce end je ve skutečnosti pouze „syntaktickým cukrem“, který byl do programovacího jazyka Lua přidán především proto, aby se jeho syntaxe přibližovala běžným imperativním jazykům. Ve skutečnosti totiž parametry funkce spolu s jejím tělem přestavují hodnotu typu function, kterou je možné přiřadit do proměnné libovolného jména (dokonce ani k přiřazení nemusí dojít, což je případ nepojmenovaných lokálních funkcí – uzávěrů). „Skutečná“ syntaxe definice funkce tedy neobsahuje její jméno, jak si ostatně ukážeme na druhém demonstračním příkladu vypsaném pod odstavcem. Ve zdrojovém textu tohoto příkladu jsou vytvořeny dvě funkce (tj. ve skutečnosti hodnoty typu function), přičemž první funkce je přiřazena proměnné pojmenované printHello a druhá funkce proměnné printWorld. Výsledkem přiřazení je vytvoření totožných pojmenovaných funkcí, jako v prvním demonstračním příkladu, ovšem pokud se nad příkladem zamyslíme, zjistíme jednu zajímavou věc – ve spuštěném programu je možné, že se hodnota libovolné proměnné může měnit a totéž samozřejmě platí i pro hodnoty typu function. Jinými slovy to znamená, že v jazyku Lua je možné funkce znovu definovat, tj. jejich jméno se sice nemění, ale tělo (tj. implementovaný algoritmus) již ano:

-- Druhy demonstracni priklad:
-- definice vlastnich funkci bez vyuziti
-- "syntaktickeho cukru"

-- funkce obsahujici lokalni promennou
-- (hodnota typu funkce je prirazena
--  promenne pojmenovane "printHello")
printHello = function()
local helloStr = "Hello"
print(helloStr)
end

worldStr="World"

-- funkce vyuzivajici globalni promennou
-- (hodnota typu funkce je prirazena
--  promenne pojmenovane "printWorld")
printWorld = function()
-- ke globalni promenne je pripojena
-- retezcova konstanta (literal)
print(worldStr .. "!")
end

-- volani obou funkci
printHello()
printWorld()

-- zmena globalni promenne
-- se projevi i ve volane funkci
worldStr=42
printWorld()

-- finito

Parametry funkcí a jejich návratové hodnoty

Prakticky každá funkce, pokud má provádět užitečnou činnost, musí nějakým způsobem komunikovat se svým okolím. V té nejjednodušší (a nejméně vhodné) podobě lze použít globální proměnné, ke kterým má funkce samozřejmě přístup – výsledný program se však sémanticky podobá spíše BASICovým konstrukcím s podprogramy a nikoli vysokoúrovňovému zápisu algoritmu, nehledě na to, že funkce používající globální proměnné, se například velmi těžko testují či používají při paralelním programování. Mnohem lepší je funkci předávat informace pomocí parametrů, jejichž jména (nikoli typy – ty jsou, jak již víme z předchozích kapitol, přiřazeny hodnotám) jsou zapsána uvnitř kulatých závorek. Funkce taktéž může vracet jednu nebo více (!) hodnot pomocí konstrukce return seznam_hodnot. Vzhledem k existenci operátoru vícenásobného přiřazení a možnosti vrátit větší množství parametrů jsou tak funkce v jazyku Lua obecnější, než v některých dalších programovacích jazycích, ve kterých funkce většinou mohou vracet jen jednu hodnotu (i díky této vlastnosti se v Lua nemusí zavádět konstrukce typu volání hodnotou a volání odkazem).

Následuje demonstrační příklad, v němž je vytvořeno a následně použito (zavoláno) několik funkcí. První funkce nazvaná repeatMessage má parametry, ale nevrací žádnou hodnotu, druhá funkce factorial má jeden parametr a vrací buď nil pro parametr, který neodpovídá definičnímu oboru faktoriálu, nebo přirozené číslo vyjadřující n! (samotný faktoriál je vypočten nerekurzivně, jeho rekurzivní zápis je uveden v navazujících kapitolách). Třetí funkce divMod má dva parametry x,y a vrací dvě celočíselné hodnoty – celočíselný podíl x div y a zbytek po dělení x mod y (někteří historici tvrdí, že právě tato matematická „dvojoperace“ byla první početní operací, kterou lidé používali; své využití měla především při rozdělování kořisti). Funkce divMod je následně použita pro vytvoření tabulky celočíselného dělení i zbytku po dělení čísla 10 hodnotami od jedné do desíti. Následuje výpis zdrojového kódu třetího demonstračního příkladu:

-- Treti demonstracni priklad:
-- funkce s parametry, funkce vracejici hodnoty

-- funkce se dvema parametry,
-- ktera nevraci zadnou hodnotu
function repeatMessage(message, count)
for i = 1, count do
print(i, message)
end
end

-- nerekurzivni vypocet faktorialu
-- (funkce s jednim parametrem vracejici taktez jednu hodnotu)
function factorial(n)
local result = 1
-- faktorial je definovan pouze pro prirozena cisla a nulu
if n < 0 then
return nil
end
for i = 1, n do
result = result * i
end
return result
end

-- funkce vracejici dve hodnoty: vysledek celociselneho
-- deleni a zbytek po celociselnem deleni
function divMod(x,y)
return math.floor(x / y), x % y
end

-- volani funkci
repeatMessage("Hello world!", 10)

print()
print("Factorial")
print("n", "n!")
for n = -5, 10 do
print(n, factorial(n))
end

print()
print("DivMod")
print("n", "10/n", "zbytek")
for n = 1, 10 do
x, y = divMod(10, n)
print(n, x, y)
end

-- finito

Výše uvedený demonstrační příklad po svém spuštění vypíše na standardní výstup následující text:

1    Hello world!
2       Hello world!
3       Hello world!
4       Hello world!

Vážení čtenáři, právě jste dočetli ukázku z knihy Programovací jazyk Lua.
Pokud se Vám líbila, celou knihu si můžete zakoupit v našem e-shopu.