Hlavní navigace

Programovací jazyk Lua

10. 3. 2009
Doba čtení: 14 minut

Sdílet

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 tomto seriálu si řekneme, proč tomu tak je.

Obsah

1. Programovací jazyk Lua
2. Stručná historie programovacího jazyka Lua
3. Licence
4. Základní vlastnosti programovacího jazyka Lua
5. Vestavění překladače a interpretru jazyka Lua do aplikací
6. Demonstrační příklad – vestavění překladače a interpretru do céčkového programu
7. Odkazy na Internetu
8. Obsah další části

1. 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á část tohoto seriálu, 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í.

lua0101

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

lua0102

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.

2. 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 – viz obrázek zobrazený v perexu článku).

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

lua0103

Obrázek 3

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.

lua0104

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

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

lua0105

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

5. 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).

6. 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 registerLuaFun­ctions(), 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 
lua0106
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() 
lua0107
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() 
lua0108
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() 
lua0109
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) 
lua0110
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) 
lua0111
clean()

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

CS24_early

7. Odkazy na Internetu

  1. Lua home page
    http://www.lu­a.org/
  2. Lua: vestavitelný minimalista
    /clanky/lua-vestavitelny-minimalista/
  3. Lua
    http://www.li­nuxexpres.cz/pra­xe/lua
  4. Lua
    http://cs.wiki­pedia.org/wiki/Lua
  5. Lua (programming language)
    http://en.wiki­pedia.org/wiki/Lu­a_(programmin­g_language)
  6. The Lua Programming Language
    http://www.ti­obe.com/index­.php/paperinfo/tpci/Lu­a.html
  7. Lua Programming Gems
    http://www.lu­a.org/gems/
  8. LuaForge
    http://luafor­ge.net/
  9. Forge project tree
    http://luafor­ge.net/softwa­remap/trove_lis­t.php
  10. gamedev.net: Lua
    http://www.ga­medev.net/refe­rence/program­ming/features/lu­a/
  11. Category:Lua-scripted games
    http://en.wiki­pedia.org/wiki/Ca­tegory:Lua-scripted_games
  12. Category:Lua-scriptable games
    http://en.wiki­pedia.org/wiki/Ca­tegory:Lua-scriptable_games
  13. BZFlag
    http://en.wiki­pedia.org/wiki/BZFlag
  14. BZFlag.org
    http://bzflag­.org/
  15. GrimE
    http://en.wiki­pedia.org/wiki/Gri­mE
  16. Grim Fandango
    http://www.mo­bygames.com/ga­me/grim-fandango
  17. Escape from Monkey Island
    http://www.mo­bygames.com/ga­me/escape-from-monkey-island

8. Obsah další části

V následující části tohoto seriálu se seznámíme se základními programovými konstrukcemi a datovými typy, které je možné použít při psaní skriptů v programovacím jazyce Lua. Především se zmíníme o práci s asociativními poli, protože tento datový typ představuje základ jak pro tvorbu „klasických“ indexovaných polí, tak i seznamů, zásobníků, front, stromů, struktur typu záznam (record v Pascalu a struct v céčku) a dokonce i objektů.

Byl pro vás článek přínosný?

Autor článku

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