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