Základní konstrukce v programovacím jazyku Lua

17. 3. 2009
Doba čtení: 13 minut

Sdílet

Ve druhé části našeho nového seriálu o programovacím jazyku Lua se společně seznámíme se základními programovými konstrukcemi a datovými typy, které jsou v tomto jazyce podporovány. Ukážeme si především práci s proměnnými, tvorbu bloků, podmíněných příkazů a také programových smyček.

Obsah

1. Základní programové konstrukce v programovacím jazyku Lua
2. Podporované datové typy
3. Proměnné a přiřazení hodnoty do proměnných
4. Bloky a jejich význam při ovlivňování viditelnosti proměnných
5. Podmínky
6. Programová smyčka typu while
7. Programová smyčka typu repeat–until
8. Dvojí podoba programové smyčky typu for

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

V dnešní části seriálu o programovacím jazyku Lua 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í části tohoto seriálu 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.

2. 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í části seriálu.

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)

3. 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 tomto seriálu 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 

4. 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 

5. 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ém z dalších pokračování tohoto seriálu (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 

6. 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 

7. 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é.

8. 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í části tohoto seriálu 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) 

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í části tohoto seriálu, je následující:

ict ve školství 24

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 

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.