Obsah
1. Projekt LuaJ: spolupráce skriptů napsaných v jazyce Lua s Javou
2. Přímé spuštění interpretru Luaj z příkazového řádku
3. Překlad skriptu do bajtkódu a následná interpretace bajtkódu
4. Načtení a spuštění skriptu z Javovské aplikace
5. První demonstrační příklad – skript spuštěný z Javovské aplikace
6. Druhý demonstrační příklad – bajtkód skriptu spuštěný z Javovské aplikace
7. JSR-223 a její význam při implementaci skriptovacích jazyků pro Javu
8. Využití tříd implementujících rozhraní ScriptEngine, ScriptEngineFactory a ScriptEngineManager
9. Literatura a odkazy na Internetu
1. Projekt LuaJ: spolupráce skriptů napsaných v jazyce Lua s Javou
V předchozí části tohoto seriálu jsme se seznámili s poměrně zdařilou implementací jazyka Lua nazvanou LuaJ. Jedná se o nástroj vytvořený v Javě, přičemž výsledný interpret i překladač je možné 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), což sice znamená, že Lua skripty běží pomaleji, než v originálním „céčkovém“ interpretru jazyka Lua, na druhou stranu však mohou vývojáři používat celou řadu Javovských knihoven, od těch standardních dodávaných spolu s JDK/JRE (Java Development Kit/Java Runtime Environment), tak i například knihoven a frameworků vyvíjených v rámci projektu Apache. Dnes si ukážeme několik způsobů využití LuaJ a taktéž se seznámíme se specifikací JSR-223, ve které je mj. popsáno univerzální aplikační programové rozhraní, které je možné použít pro přístup k prakticky libovolnému skriptovacímu jazyku, jehož interpret běží na platformě J2SE.
2. Přímé spuštění interpretru Luaj z příkazového řádku
Programátor, který se rozhodne využívat Lua skripty pro rozšíření své javovské aplikace, má k dispozici několik způsobů, jakými může Lua skript spouštět. Nejjednodušší způsob, který jsme si již ukázali v předchozí části tohoto seriálu, spočívá v přímém spuštění interpretru či překladače, který je dostupný v archivu luaj-j2se-verze.jar popř. luaj-j2me-verze.jar (místo slova verze se samozřejmě musí dosadit konkrétní číslo verze, tj. například 0.96). Tyto dva archivy, jenž jsou dostupné na stránce projektu LuaJ, obsahují interpret a překladač jazyka Lua i všechny potřebné základní knihovny, které známe z céčkové verze Lua.
Předností tohoto způsobu spouštění skriptů je značná jednoduchost (není zapotřebí spouštět překladač javac ani nastavovat cesty) a v neposlední řadě také to, že není nutné, aby na počítači bylo nainstalované celé vývojové prostředí jazyka Java – ve skutečnosti postačuje pouze JRE (Java Runtime Environment) verze 1.5, takže se v mnoha případech zjednoduší i instalace aplikace. V případě, že je nutné přímo spustit nějaký skript napsaný v jazyce Lua, předá se název tohoto skriptu jako první parametr interpretru. V níže uvedeném příkladu představuje lua název javovské třídy s interpretrem uložené v archivu luaj-j2se-0.96.jar, zatímco test.lua je název spouštěného skriptu. V případě, že má skript využívat další třídy, musí se nastavit cesta (classpath) i k těmto třídám, popř. k archivu, ve kterém jsou přeložené třídy zabaleny:
java -cp luaj-j2se-0.96.jar lua test.lua
3. Překlad skriptu do bajtkódu a následná interpretace bajtkódu
Druhá možnost spuštění Lua skriptů spočívá v jejich překladu do bajtkódu virtuálního stroje jazyka Lua s následnou interpretací tohoto bajtkódu. V praxi je tedy možné, aby byla celá aplikace či její část dodávána ve formě binárních (a běžným způsobem nečitelných) souborů *.luac, ve kterých je uložen již přeložený bajtkód Lua skriptů (pozor – v žádném případě se nejedná o bajtkód interpretovaný pomocí JVM!). Toto řešení má několik předností a samozřejmě i záporů. Mezi přednosti patří, že opět není nutné, aby na počítači bylo nainstalováno celé JDK, postačuje pouze JRE. Taktéž se fáze překladu skriptů provede pouze jedenkrát – uživatelé aplikace budou využívat již přeložený bajtkód, což může například v případě mobilních zařízení představovat jak určitou úsporu potřebné kapacity operační paměti (aplikace nemusí obsahovat překladač luac, což znamená úsporu cca 100 kB) i času startu aplikace. Překlad Lua skriptu do bajtkódu je možné provést buď původním „céčkovým“ překladačem luac nebo jeho javovskou verzí:
luac -o test1.luac test.lua java -cp luaj-j2se-0.96.jar luac -o test2.luac test.lua
Obsah souborů s bajtkódem sice většinou nemá stejný obsah, o čemž se můžeme přesvědčit například příkazem diff, ovšem interpret LuaJ dokáže (alespoň v současné verzi) zpracovat oba bajtkódy:
java -cp luaj-j2se-0.96.jar lua test1.luac java -cp luaj-j2se-0.96.jar lua test2.luac
4. Načtení a spuštění skriptu z Javovské aplikace
Výše uvedený způsob kooperace mezi Lua skripty a třídami naprogramovanými v jazyce Java je vhodné použít v těch případech, kdy Lua skript představuje řídicí část celé aplikace („lepidlo“), které na základě zapsaného algoritmu postupně vytváří instance javovských tříd a poté přistupuje k metodám a atributům těchto tříd. V určitém ohledu se tento přístup podobá například shellovým skriptům nebo skriptům napsaným v jazyce TCL, jenž taktéž postupně volají jednoduché externí utility či celé aplikace. V mnoha případech je však vhodnější zvolit zcela opačný postup, při kterém se přímo v aplikaci naprogramované v jazyce Java spustí interpret jazyka Lua. Tento přístup je sice implementačně poněkud složitější, umožňuje však rozšíření takřka libovolného programu o možnost skriptování, což – jak se v historii výpočetní techniky již několikrát ukázalo – může představovat významnou konkurenční výhodu aplikace (viz například AutoCAD, kancelářské balíky či webové prohlížeče s JavaScriptem). Ve skutečnosti existují dokonce dvě možnosti zabudování interpretru jazyka Lua do aplikací, které si popíšeme v následujících kapitolách.
5. První demonstrační příklad – skript spuštěný z javovské aplikace
První ze způsobů spuštění skriptu naprogramovaného v jazyce Lua z javovské aplikace je ukázán na jednoduchém příkladu, jehož zdrojový text je uveden pod tímto odstavcem. Jedná se o javovskou aplikaci, která po svém spuštění vytvoří instanci třídy reprezentující stav virtuálního stroje jazyka Lua a posléze zavolá metodou vm.call() s parametry odpovídajícími volání dofile(„fib.lua“) z Lua skriptu (této funkci je předaný jediný parametr a funkce nevrací žádnou hodnotu; proto se také při volání metody vm.call() předává jednička v prvním argumentu a nula v argumentu druhém). Globální funkci dofile() již známe z předchozích částí tohoto seriálu – načte zdrojový soubor se skriptem či jeho bajtkód, poté provede překlad zdrojového souboru a následně spuštění bajtkódu (tj. jeho interpretaci virtuálním strojem). Povšimněte si taktéž příkazu org.luaj.compiler.LuaC.install();. Pomocí tohoto příkazu se zajistí vytvoření instance překladače jazyka Lua, takže je možné, aby globální funkce dofile() skutečně dokázala načíst a zpracovat i skripty ve zdrojovém tvaru, tj. nikoli pouze již předem přeložený bajtkód.
/** * Jednoducha javovska aplikace, ktera po svem spusteni * nacte skript ulozeny v souboru "fib.lua" a nasledne * ho spusti. **/ // Preklad tridy: // javac -cp luaj-j2se-verze.jar FibTest.java // // Spusteni programu se skriptem: // java -cp luaj-j2se-verze.jar;. FibTest // popr. // java -cp luaj-j2se-verze.jar:. FibTest import org.luaj.platform.*; import org.luaj.vm.*; public class FibTest { public static void main(String[] args) { // jmeno skriptu String script = "fib.lua"; Platform.setInstance( new J2sePlatform() ); // vytvoreni instance virtualniho stroje // jazyka Lua LuaState vm = Platform.newLuaState(); // vytvoreni instance prekladace // jazyka Lua org.luaj.compiler.LuaC.install(); // nacteni skriptu vm.getglobal( "dofile" ); vm.pushstring( script ); // spusteni skriptu - zavolani funkce dofile // ktera akceptuje jeden parametr (1) a nevraci // zadny vysledek (0) vm.call( 1, 0 ); } } // finito
6. Druhý demonstrační příklad – bajtkód skriptu spuštěný z javovské aplikace
Ve druhém demonstračním příkladu si ukážeme, jakým způsobem lze pracovat s Lua skriptem, který je již před spuštěním aplikace přeložený do bajtkódu postupem popsaným ve třetí kapitole. Tento příklad se v mnoha ohledech podobá předchozímu demonstračnímu příkladu, ovšem s tím rozdílem, že se zde neprovádí inicializace překladače jazyka Lua (ve skutečnosti nemusí být překladač vůbec přístupný, tj. spolu s aplikací se nemusí dodávat ani příslušné třídy) a navíc se globální funkci dofile() nepředává název zdrojového kódu skriptu, ale název souboru, ve kterém je uložený jeho bajtkód. Povšimněte si, že při spuštění této aplikace je nutné pomocí volby -cp nastavit cestu ke knihovnám interpretru (ty jsou uloženy, spolu se standardními knihovnami, v archivu luaj-j2se-verze.jar) i cestu, na které je uložena samotná aplikace. V tomto jednoduchém případě se jedná o aktuální adresář, proto je ve volbě -cp použita i tečka oddělená od jména archivu buď středníkem (Windows) nebo dvojtečkou (unixové systémy):
/** * Jednoducha javovska aplikace, ktera po svem spusteni * nacte a spusti skript prelozeny do bajtkodu, ktery * je ulozen v souboru "fib.luac" **/ // Preklad tridy: // javac -cp luaj-j2se-verze.jar FibTest2.java // // Spusteni programu s prelozenym skriptem: // java -cp luaj-j2se-verze.jar;. FibTest2 // popr. // java -cp luaj-j2se-verze.jar:. FibTest2 import org.luaj.platform.*; import org.luaj.vm.*; public class FibTest2 { public static void main(String[] args) { // jmeno skriptu String compiledScript = "fib.luac"; Platform.setInstance( new J2sePlatform() ); // vytvoreni instance virtualniho stroje // jazyka Lua LuaState vm = Platform.newLuaState(); // nacteni skriptu vm.getglobal( "dofile" ); vm.pushstring( compiledScript ); // spusteni skriptu - zavolani funkce dofile // ktera akceptuje jeden parametr (1) a nevraci // zadny vysledek (0) vm.call( 1, 0 ); } } // finito
7. JSR-223 a její význam při implementaci skriptovacích jazyků pro Javu
Ve čtvrté kapitole jsme si mj. řekli, že existují dva způsoby spuštění skriptů napsaných v programovacím jazyce Lua z Javy. První způsob jsme si ukázali na dvojici demonstračních příkladů, ve kterých se přímo využívaly třídy dostupné z balíčků org.luaj.platform a org.luaj.vm. Existuje však ještě jeden způsob volání skriptů, jenž má tu výhodu, že je při něm využito standardního API, které je použitelné kromě samotného jazyka Lua i pro poměrně velkou skupinu dalších skriptovacích jazyků naprogramovaných v Javě. Aplikační programové rozhraní, pomocí něhož mezi sebou mohou komunikovat skripty a javovské programy, je popsané v JSR-223 neboli Java Specification Request – Scripting for the JavaTM Platform. Existence JSR-223 je velmi důležitá, protože význam dynamicky typovaných skriptovacích jazyků neustále roste, společně s rostoucí složitostí vytvářených aplikací. V současnosti je možné přes toto API využít již velkou skupinu programovacích jazyků, především Python (Jython), Groovy, Ruby (JRuby) či TCL (JACL) – viz následující tabulku:
Jazyk | Název implementace | Popis |
---|---|---|
AWK | Jawk | implementace jazyka awk, který se v unixových systémech používá pro vyhledávání vzorů a zpracování textů |
BeanShell | BeanShell | dynamicky typovaný skriptovací jazyk se syntaxí podobnou samotné Javě |
Groovy | Groovy | taktéž se jedná o dynamicky typovaný skriptovací jazyk se syntaxí podobnou Javě, ovšem na rozdíl od BeanShellu je Groovy už dnes mezi vývojáři poměrně populární |
Jaskell | Jaskell | funkcionální jazyk odvozený od standardního Haskellu, ovšem s přidanou podporou pro javovské třídy |
JavaScript | Rhino | pravděpodobně nejznámější skriptovací jazyk s rozhraním dle JSR-223, který je dodáván přímo se sunovskou Javou SE 6 |
Jelly | Jelly | nástroj pro transformaci XML do spustitelného kódu, podporuje značky použitelné pro popis základních programových konstrukcí – smyčka, rozhodování atd. |
JudoScript | JudoScript | málo rozšířený skriptovací jazyk |
Pnuts | Pnuts | jednoduchý a snadno použitelný skriptovací jazyk |
Python | Jython | implementace známého a oblíbeného programovacího jazyka Python pro platformu Java |
Ruby | JRuby | dnes populární skriptovací jazyk s podporou OOP |
Scheme | SISC | programovací jazyk inspirovaný Lispem, ovšem (na rozdíl od klasického Lispu) se statickým rozsahem proměnných a poněkud odlišnou sémantikou některých konstrukcí |
Sleep | Sleep | jazyk inspirovaný Perlem |
Tck | Jacl | implementace známého skriptovacího jazyka Tcl (Tool Command Language), který vytvořil známý popularizátor skriptovacích jazyků John K. Ousterhout (mj. spoluzakladatel společnosti Scriptics) |
Celé API specifikované v JSR-223 je představováno několika rozhraními a (abstraktními) třídami z balíčku javax.script, z nichž ty nejdůležitější jsou vypsány v následujících dvou tabulkách:
Rozhraní |
---|
Bindings |
Compilable |
Invocable |
ScriptContext |
ScriptEngine |
ScriptEngineFactory |
Třída |
---|
AbstractScriptEngine |
CompiledScript |
ScriptEngineManager |
SimpleBindings |
SimpleScriptContext |
ScriptException |
Základem je třída ScriptEngineManager, pomocí níž lze manipulovat s jednotlivými interpretry, které jsou nainstalované. Například následujícím velmi jednoduchým kódem je možné zjistit, které interpretry jsou v dané chvíli k dispozici, verze interpretrů, jejich aliasy a především instanci třídy implementující rozhraní ScriptEngine, kterou lze použít pro přístup k vybranému interpretru:
ScriptEngineManager mgr = new ScriptEngineManager(); List<ScriptEngineFactory> factories = mgr.getEngineFactories();
Mnohem častěji však programátor potřebuje ve svém projektu použít předem známý interpretr (jehož jméno tudíž má k dispozici a nemusí ho zjišťovat) a spustit pomocí něho nějaký skript. V následujícím kódu je inicializován interpretr JavaScriptu, který je, jak jsme si již řekli v předchozích odstavcích, dodáván spolu se sunovskou Javou 1.6 jako její standardní součást (jedná se o známou implementaci Mozilla Rhino):
ScriptEngineManager mgr = new ScriptEngineManager(); ScriptEngine jsEngine = mgr.getEngineByName("JavaScript"); try { jsEngine.eval("print('Hello, world!')"); } catch (ScriptException ex) { ex.printStackTrace(); }
8. Využití tříd implementujících rozhraní ScriptEngine, ScriptEngineFactory a ScriptEngineManager
V dnešním posledním úryvku programového kódu je ukázáno, jakým způsobem je možné zajistit vytvoření instance překladače a interpretru jazyka Lua pomocí API definovaného v JSR-223. Povšimněte si, že konkrétní interpret lze vyhledat jak podle svého jména (předchozí kapitola), tak i na základě koncovky jmen souborů se skripty. Aby bylo možné následující kód spustit bez vyvolání výjimky, je nutné, aby se při spuštění aplikace specifikovala cesta k archivu luaj-j2se-verze.jar, podobně jako v případě prvních dvou demonstračních příkladů:
ScriptEngineManager sem = new ScriptEngineManager(); ScriptEngine e = sem.getEngineByExtension(".lua"); ScriptEngineFactory f = e.getFactory(); System.out.println( "Engine name: " +f.getEngineName() ); System.out.println( "Engine Version: " +f.getEngineVersion() ); System.out.println( "LanguageName: " +f.getLanguageName() ); System.out.println( "Language Version: " +f.getLanguageVersion() ); String statement = f.getOutputStatement("\"hello, world\""); System.out.println(statement); try { e.eval(statement); e.put("x", 25); e.eval("y = math.sqrt(x)"); System.out.println( "y="+e.get("y") ); e.put("x", 2); e.eval("y = math.sqrt(x)"); System.out.println( "y="+e.get("y") ); } catch (ScriptException ex) { ex.printStackTrace(); }
9. Literatura a odkazy na Internetu
- James Roseborough, Ian Farmer: Getting Started with LuaJ:
dokument obsažený přímo v instalaci LuaJ - SourceForge Luaj Project Page:
http://luaj.sourceforge.net/ - SourceForge Luaj Download Area:
http://sourceforge.net/project/platformdownload.php?group_id=197627 - LuaForge Luaj Project Page:
http://luaforge.net/projects/luaj/ - LuaForge Luaj Project Area:
http://luaforge.net/frs/?group_id=457 - JSR 223: Scripting for the JavaTM Platform:
http://www.jcp.org/en/jsr/detail?id=223 - Scripting for the Java Platform:
http://java.sun.com/developer/technicalArticles/J2SE/Desktop/scripting/ - JSR-000223 Scripting for the JavaTM Platform (Proposed Final Draft):
http://jcp.org/aboutJava/communityprocess/pfd/jsr223/index.html - Scripting for Java (scripting.dev.java.net):
https://scripting.dev.java.net/ - Rhino: JavaScript for Java:
http://www.mozilla.org/rhino/ - Apache Software Foundation:
http://www.apache.org - LuaForge:
http://luaforge.net/ - LuaForge project tree:
http://luaforge.net/softwaremap/trove_list.php