LuaJ – implementace jazyka Lua v Javě

21. 7. 2009
Doba čtení: 10 minut

Sdílet

V dnešní části seriálu o programovacím jazyku Lua si ukážeme, jakým způsobem je možné využít jednu z poměrně zdařilých implementací tohoto jazyka. Jedná se o implementaci nazvanou příhodně LuaJ, protože je vytvořena, na rozdíl od originální "céčkovské" Luy, v programovacím jazyku Java.

Obsah

1. LuaJ – implementace jazyka Lua v Javě
2. Přednosti a zápory implementace LuaJ
3. Instalace LuaJ a spouštění skriptů
4. Testovací skript pro zjištění rozdílu mezi Lua a LuaJ v rychlosti interpretace skriptů
5. Časy běhu skriptu
6. Spolupráce s knihovnami jazyka Java
7. Demonstrační příklad – vytvoření okna pomocí knihovny Swing
8. Odkazy na Internetu

1. LuaJ – implementace jazyka Lua v Javě

V předchozích částech tohoto seriálu jsme se převážně zabývali popisem použití interpretru jazyka Lua, který je napsán v ANSI (ISO) C – ostatně i systém LÖVE je určen pro céčkovou variantu Luy. Jedná se o původní implementaci tohoto jazyka, která je přenositelná na velké množství platforem, od jednoduchých mikrořadičů až po výkonná PC. Kromě této implementace však existuje i poměrně zdařilá implementace jazyka Lua nazvaná příhodně LuaJ, protože je vytvořena v programovacím jazyku Java, přičemž výsledný interpret lze použít jak pro platformu J2SE/J2EE (desktopy, aplikační servery atd.), tak i J2ME (rozličná mobilní zařízení). Ve své podstatě se jedná o virtuální stroj (Lua Virtual Machine) běžící ve druhém virtuálním stroji (JVM – Java Virtual Machine). Použitím platformy Java je mj. umožněno, aby vytvářené skripty využívaly bez větších omezení všech knihoven poskytovaných běhovým prostředím Javy (JRE), podobně jako tomu je například v případě skriptovacího jazyka Scala či Jythonu (poměrně oblíbená implementace Pythonu pro JRE).

Dokonce je možné použít interpret jazyka Lua jako standardní skriptovací engine odpovídající JSR-233 (dynamické skriptování, viz odkazy) – právě tímto způsobem je v Javě 6 implementován JavaScript, jedná se o projekt Mozilla Rhino. Javovská verze jazyka Lua si přitom zachovává svoji relativně malou velikost – celý archiv obsahující jak interpret, tak i překladač, má necelých 160 kB (adresa, ze které se tento archiv dá stáhnout, je uvedená v poslední kapitole). Varianta pro platformu J2ME je dokonce ještě o 45 kB menší, protože neobsahuje všechny knihovny. V dalším textu se budu zabývat především variantou určenou pro platformu J2SE, která může využívat všechny knihovny, které běhové prostředí J2SE nabízí, například knihovny AWT a Swing pro tvorbu grafického uživatelského rozhraní, rozsáhlé knihovny pro práci se soubory a sítěmi (IO, NIO, …), JDBC (rozhraní k relačním databázím), RMI (volání metod vzdálených objektů), JNDI atd. Samozřejmě lze LuaJ použít i s dalšími knihovnami i celými frameworky, například přidat podporu pro skriptování v Lua do aplikačních serverů (JBoss, WebSphere) atd.

2. Přednosti a zápory implementace LuaJ

Spojení platformy Javy a jazyka Lua je v několika ohledech výhodné. Programátoři pracující v jazyce Lua tak získávají přístup k velkému množství různých knihoven, ať již těch standardních (některé jsme si vypsali v předchozím odstavci), tak i například knihoven vyvíjených v rámci Apache Software Foundation, což je jistě významné rozšíření možností tohoto jazyka, především v porovnání s minimalisticky pojatými standardními knihovnami (coroutine, debug, file, io, os, string, math, table a package). Na druhou stranu mohou z tohoto spojení profitovat i programátoři v Javě, protože dostávají do rukou další výkonný jazyk, který v mnoha ohledech ideálně doplňuje samotnou staticky typovanou Javu o dynamický skriptovací jazyk umožňující v mnoha případech rychlejší vývoj a testování – to je ostatně také důvod, proč se dnes pozornost vývojářů zaměřuje i na další dynamické jazyky určené pro platformu Javy, především na Scalu a Jython. Mezi nevýhody lze počítat především ne zcela dokonalou zpětnou kompatibilitu s původními knihovnami jazyka Lua, což může znamenat, že některé skripty je nutné upravit (nekompatibilita je z velké míry způsobena rozdílem mezi rozhraním operačního systému a rozhraním poskytovaným JRE).

3. Instalace LuaJ a spouštění skriptů

Instalace interpretru i překladače LuaJ je velmi jednoduchá a lze ji provést na každém počítači, který obsahuje JRE (Java Runtime Environment) alespoň verze 1.5. Po stažení archivu z adresy http://luaj.sou­rceforge.net/ je zapotřebí tento archiv rozbalit, například nástrojem unzip. V archivu se nachází zdrojové kódy celého interpretru i překladače, dále pak soubory s testy a nakonec také – což je z našeho pohledu nejdůležitější – dva archivy ve formátu JAR – luaj-j2se-verze.jar a luaj-j2me-verze.jar. Tyto dva archivy obsahují interpret a překladač jazyka Lua i všechny základní knihovny. Pro spuštění interpretru lze použít následující příkaz, ve kterém je zapotřebí nahradit číslo 0.96 aktuální verzí souboru:

java -cp luaj-j2se-0.96.jar lua

Pokud je nutné přímo spustit nějaký skript napsaný v jazyce Lua, předá se název skriptu jako první parametr interpretru:

java -cp luaj-j2se-0.96.jar lua fib.lua

Popř. je možné skript přeložit do bajtkódu jazyka Lua (soubory s bajtkódem ovšem nemusí být přenositelné na céčkovou verzi interpretru):

java -cp luaj-j2se-0.96.jar luac fib.lua

4. Testovací skript pro zjištění rozdílu mezi Lua a LuaJ v rychlosti interpretace skriptů

Pro alespoň rámcové zjištění rozdílů rychlosti interpretace Lua skriptů různými interpretry („céčková“ lua vs. „javovská“ luaj) posloužil následující program, který po svém spuštění vypíše na standardní výstup prvních 25 členů Fibonacciho posloupnosti i časy výpočtu jednotlivých členů. Pro každý člen posloupnosti se zvlášť volá rekurzivní funkce bez ohledu na předchozí výsledky (které by bylo samozřejmě možné využít), což je samozřejmě neefektivní, ovšem na druhou stranu se lépe měří časové rozdíly intepretace tohoto programu. V testovacím skriptu se mj. používá i metoda clock() z knihovny os. Tato knihovna je sice v „céčkové“ verzi interpretru přímo dostupná, ovšem při použití Javovské verze, tj. luaj, je nejdříve nutné tuto knihovnu explicitně načíst, o což se postará příkaz pcall(require, „org.luaj.lib­.j2se.J2seOsLib“). Zjištění, zda je knihovna os dostupná je snadné – jedná se o globální asociativní pole, takže lze použít jednoduchý test, zda existuje globální proměnná os:

-- Výpočet Fibonacciho posloupnosti s využitím
-- (neefektivního) rekurzivního algoritmu


-- nutné pouze při volání skriptu z LuaJ,
-- při použití "céčkové" Luy lze zakomentovat
print('os lib loaded: ', os ~= nil)
local lib = "org.luaj.lib.j2se.J2seOsLib"
print('require "'..lib..'"', pcall(require, lib) )
print('os lib loaded: ', os ~= nil)


-- funkce provádějící vlastní rekurzivní výpočet
-- Fibonacciho posloupnosti
function fib(n)
    if n < 2 then
        return n
    else
        return fib(n - 1) + fib(n - 2)
    end
end



-- funkce, která vypočte a vytiskne n-té číslo
-- Fibonacciho posloupnosti
-- spolu s měřením času výpočtu
function test(n)
    local time1 = os.clock()
    -- volat stejnou funkci 100x, pro zlepšení
    -- přesnosti výpočtu času
    local value
    for i = 1, 100 do
        value = fib(n)
    end
    local time2 = os.clock()
    local deltaTime = time2 - time1
    -- tisk výsledků výpočtu i měření času
    print(n, value, math.floor(1000.0*time1), math.floor(1000.0*time2), math.floor(1000.0*deltaTime))
end



-- výpis hlavičky tabulky
print("n", "value", "time1", "time2", "delta")

for n = 1, 25 do
    test(n)
end

-- finito

5. Časy běhu skriptu

Časy výpočtu získané interpretací testovacího skriptu s využitím původní „céčkové“ varianty jazyka Lua, přeložené s využitím překladače GCC pomocí dodávaného souboru Makefile, jsou vypsány v následující tabulce. Poznamenejme, že v souboru Makefile je mj. použita volba -O2, tj. kód interpreteru je optimalizován, i když se nejedná o nejúčinnější metodu optimalizace:

n       value   time1   time2   delta
1       1       0       0       0
2       1       0       0       0
3       2       0       0       0
4       3       0       0       0
5       5       0       10      10
6       8       10      10      0
7       13      10      10      0
8       21      10      10      0
9       34      10      20      10
10      55      20      30      9
11      89      30      40      10
12      144     40      60      19
13      233     60      100     40
14      377     100     160     60
15      610     160     260     100
16      987     260     420     159
17      1597    420     670     250
18      2584    670     1091    420
19      4181    1091    1762    671
20      6765    1762    2864    1101
21      10946   2864    4656    1791
22      17711   4656    7520    2864
23      28657   7520    12157   4637
24      46368   12157   19598   7440
25      75025   19598   31625   12027

V další tabulce jsou vypsány časy interpretace skriptu pomocí Javovského interpreteru LuaJ, který byla spuštěn na platformě Java 6 v režimu client (volba -client). Časy běhu jsou cca 2,5× delší, než ve výše uvedeném případě:

n       value   time1   time2   delta
1       1       10      10      0
2       1       10      50      40
3       2       50      60      9
4       3       60      80      20
5       5       80      80      0
6       8       80      80      0
7       13      80      90      9
8       21      90      100     10
9       34      100     120     19
10      55      120     150     30
11      89      150     190     40
12      144     190     260     70
13      233     260     380     120
14      377     380     561     181
15      610     561     861     299
16      987     861     1342    481
17      1597    1342    2123    781
18      2584    2123    3375    1251
19      4181    3375    5398    2022
20      6765    5398    8662    3264
21      10946   8662    13950   5287
22      17711   13950   22512   8562
23      28657   22512   36352   13839
24      46368   36352   58855   22503
25      75025   58855   95247   36392

Pokud se při spuštění interpreteru LuaJ povolí režim server (volba -server), výpočet, tj. doba interpretace skriptu, se podle očekávání zrychlí, ovšem nedosahuje takové rychlosti, jako při použití céčkové varianty interpreteru:

n       value   time1   time2   delta
1       1       10      10      0
2       1       10      30      19
3       2       30      40      10
4       3       40      651     611
5       5       651     671     20
6       8       671     701     29
7       13      701     772     71
8       21      772     842     69
9       34      852     862     10
10      55      862     882     20
11      89      882     922     40
12      144     922     972     49
13      233     972     1062    90
14      377     1062    1202    139
15      610     1202    1433    231
16      987     1433    1803    369
17      1597    1803    2404    601
18      2584    2404    3365    961
19      4181    3365    4928    1562
20      6765    4928    7471    2543
21      10946   7481    11567   4086
22      17711   11567   18197   6629
23      28657   18197   28942   10745
24      46368   28942   46397   17455
25      75025   46407   74488   28081

Po vynesení do grafu můžeme vidět rozdíl v rychlosti interpretace mezi originálním interpretrem Lua a interpretrem LuaJ spuštěného jak v režimu client, tak i server. Důležité jsou samozřejmě pouze poměry mezi jednotlivými časy, nikoli jejich absolutní hodnota (ta se bude lišit v závislosti na použitém počítači). Samozřejmě, že pokud skript většinu svého času stráví prováděním vstupně/výstupních operací, komunikací po síti, práci s GUI či čtením dat z databáze, tak se rozdíly mezi jednotlivými implementacemi zmenšují.

lua2001

6. Spolupráce s knihovnami jazyka Java

V předchozích kapitolách jsme si řekli, že jedna z největších předností integrace jazyka Lua s Javou spočívá v možnosti využití prakticky všech Javovských knihoven z Lua skriptů, tj. přímo ve skriptu je možné vytvořit instanci libovolné Javovské třídy, přistupovat k atributům i metodám této instance, registrovat callback funkce zavolané ve chvíli, kdy uvnitř JVM (Java Virtual Machine) vznikne nějaká událost atd. Většina této funkcionality je obsažena v knihovně luajava, která programátorům nabízí funkci luajava.newIn­stance() určenou vytvoření instance Java třídy zadané svým jménem s předáním parametrů do konstruktoru, funkci luajava.create­Proxy(), která zajistí vytvoření Javovského objektu, jenž může zachytit události vzniklé v JVM a zavolat Lua funkci zaregistrovanou k tomuto typu události aj. Funkce dostupné v knihovně luajava jsou založeny na Reflection API, což mj. znamená, že tato knihovna není plně funkční na platformě J2ME (mobilní zařízení) a vytvoření instancí Javovských tříd je poněkud složitější, což si ukážeme v navazující části tohoto seriálu.

7. Demonstrační příklad – vytvoření okna pomocí knihovny Swing

V dnešním demonstračním příkladu, jehož výpis je uveden pod tímto odstavcem, je ukázáno, jakým způsobem je možné vytvářet instance tříd z Javovských knihoven a jak je možné volat statické metody (tj. funkce) těchto tříd i (nestatické) metody objektů. Na tomto příkladu je pravděpodobně nejzajímavější právě způsob vytváření instance tříd pomocí funkce luajava.newIn­stance(), protože při volání této funkce je nutné uvést plné jméno třídy ve formě řetězce (o tom, která třída se skutečně použije, se rozhoduje až za běhu skriptu) a popř. i parametry předávané konstruktoru. Vzhledem k tomu, že Lua je dynamicky typovaný jazyk, neuvádí se samozřejmě typ proměnné, ve které je uložena instance třídy (Lua ani neobsahuje žádnou syntaktickou konstrukci pro určení typu proměnné). Volání metod instance třídy se provádí pomocí operátoru :, který, jak již víme z předchozích částí tohoto seriálu, představuje pouze syntaktický cukr k operátoru . – jediný rozdíl spočívá v tom, že se při použití operátoru : automaticky doplní před první argument volané funkce parametr s instancí třídy.

bitcoin_skoleni

K atributům objektů se přistupuje podobným způsobem, ovšem v tomto případě se používá se operátor ., viz například řádek s kódem frame:getConten­tPane():add(pa­ne, borderLayout.CENTER ). Zajímavé je také to, že je možné přes funkci luajava.create­Proxy() zaregistrovat callback funkci (napsanou v Lua skriptu) zavolanou v případě výskytu nějaké události. V níže uvedeném demonstračním příkladu se jedná o událost vzniklou při kliknutí myší do vytvořeného okna (callback funkce je zaregistrována pro celé okno – frame). Samotné zaregistrování události vzniklé v JVM (Java Virtual Machine) v tomto případě zajistí knihovna luajava, která automaticky vytvoří a přes příslušné API zaregistruje neviditelnou Javovskou třídu, jenž vzniklou událost zachytí a po zachycení události se zavolá funkce napsaná v Lua skriptu. Při běžném použití je tento způsob zpracování událostí pro programátory transparentní.

frame = luajava.newInstance( "javax.swing.JFrame", "Texts" );
pane = luajava.newInstance( "javax.swing.JPanel" );
borderFactory = luajava.bindClass( "javax.swing.BorderFactory" )
border = borderFactory:createEmptyBorder( 30, 30, 10, 30 )
pane:setBorder( border )
label = luajava.newInstance( "javax.swing.JLabel", "This is a Label" );

layout = luajava.newInstance( "java.awt.GridLayout", 2, 2 );
pane:setLayout( layout )
pane:add( label )
pane:setBounds( 20, 30, 10, 30 )

borderLayout = luajava.bindClass( "java.awt.BorderLayout" )
frame:getContentPane():add(pane, borderLayout.CENTER )
jframe = luajava.bindClass( "javax.swing.JFrame" )
frame:setDefaultCloseOperation(jframe.EXIT_ON_CLOSE)
frame:pack()
frame:setVisible(true)

local listener = luajava.createProxy("java.awt.event.MouseListener",
    {
        mouseClicked = function(me)
            print("clicked!", me)
        end
    })

frame:addMouseListener(listener)

8. Odkazy na Internetu

  1. James Roseborough, Ian Farmer: Getting Started with LuaJ
    dokument obsažený přímo v instalaci LuaJ
  2. SourceForge Luaj Project Page
    http://luaj.sou­rceforge.net/
  3. SourceForge Luaj Download Area
    http://source­forge.net/pro­ject/platformdow­nload.php?grou­p_id=197627
  4. LuaForge Luaj Project Page
    http://luafor­ge.net/projec­ts/luaj/
  5. LuaForge Luaj Project Area
    http://luafor­ge.net/frs/?gr­oup_id=457
  6. Lua home page
    http://www.lu­a.org/
  7. Lua: vestavitelný minimalista
    http://www.ro­ot.cz/clanky/lua-vestavitelny-minimalista/
  8. Lua
    http://www.li­nuxexpres.cz/pra­xe/lua
  9. Lua
    http://cs.wiki­pedia.org/wiki/Lua
  10. Lua (programming language)
    http://en.wiki­pedia.org/wiki/Lu­a_(programmin­g_language)
  11. The Lua Programming Language
    http://www.ti­obe.com/index­.php/paperinfo/tpci/Lu­a.html
  12. Lua Programming Gems
    http://www.lu­a.org/gems/
  13. LuaForge
    http://luafor­ge.net/
  14. Forge project tree
    http://luafor­ge.net/softwa­remap/trove_lis­t.php
  15. JSR 223: Scripting for the JavaTM Platform:
    http://jcp.or­g/en/jsr/deta­il?id=223
  16. Apache Software Foundation:
    http://www.apache­.org

Autor článku

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