Hlavní navigace

LuaJ a skriptování podle specifikace JSR-223

28. 7. 2009
Doba čtení: 12 minut

Sdílet

V dnešní části seriálu o programovacím jazyku Lua si ukážeme různé způsoby využití interpretru a překladače LuaJ, což je implementace jazyka Lua určená pro "Javovské" platformy J2SE i J2ME. Budeme se též zabývat využitím tohoto interpretru ve formě odpovídající API specifikovaného v JSR-223.

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 ScriptEngineMa­nager
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.compi­ler.LuaC.insta­ll();. 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. spoluzakla­datel 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
AbstractScrip­tEngine
CompiledScript
ScriptEngineManager
SimpleBindings
SimpleScriptContext
ScriptException

Základem je třída ScriptEngineMa­nager, 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();
}

CS24_early

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

  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. JSR 223: Scripting for the JavaTM Platform:
    http://www.jcp­.org/en/jsr/de­tail?id=223
  7. Scripting for the Java Platform:
    http://java.sun­.com/developer/techni­calArticles/J2SE/Des­ktop/scriptin­g/
  8. JSR-000223 Scripting for the JavaTM Platform (Proposed Final Draft):
    http://jcp.or­g/aboutJava/com­munityprocess/pfd/jsr223­/index.html
  9. Scripting for Java (scripting.dev­.java.net):
    https://scrip­ting.dev.java­.net/
  10. Rhino: JavaScript for Java:
    http://www.mo­zilla.org/rhi­no/
  11. Apache Software Foundation:
    http://www.apache­.org
  12. LuaForge:
    http://luafor­ge.net/
  13. LuaForge project tree:
    http://luafor­ge.net/softwa­remap/trove_lis­t.php

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.