III. A NetREXX használata szkriptnyelvként

Bevezetés

Ebben a leckében a NetREXX szkriptnyelvként történô használatáról lesz szó. Ez azt jelenti, hogy egyelôre nem (vagy csak alig) fogjuk kihasználni a NetREXX objektum orientált szolgáltatásait. A példaprogramok egyszerû felépítésûek lesznek, s a hagyományos módon, függvényekbôl és eljárásokból fogunk építkezni. A végeredményként generált Java kód természetesen könnyen hordozható, így a NetREXX-ben írt szkriptek elvileg minden platformon futtathatók lesznek, amelyek rendelkeznek Java motorral.

A szkriptek felépítése

A NetREXX szkriptekre jellemzô, hogy írásuk során nem törôdünk az osztályok definiálásával, hanem csak egyszerûen belekezdünk a program megírásába, s a NetREXX környezet automatikusan elintézi a többit. Az elsô leckében bemutatott példaprogramok többsége is tulajdonképpen szkript volt. Most tekintsük meg az alábbi példát, amely egy egyszerû (ám tanulságos), "gondoltam egy számot" alapú játékot valósít meg:

/* játék */

say 'Választok egy számot 0 és 1000 között.'
number = 1000 * Math.random() % 1 -- egész számmá alakítjuk
say 'Kész vagyok!'
guess = int

loop count = 1 until guess = number
 	say count'. tipp? \-'
 	guess = ask
 	select
 		when guess > number then
 			say 'A(z) 'guess' túl nagy.'
 		when guess < number then
 			say 'A(z) 'guess' túl kicsi.'
 		otherwise
 			say
 			say 'Gratulálok! A kérdést' count 'próbálkozás után oldotta meg.'
 	end
 	catch RunTimeException
 	say 'Sajnálom, vesztett. Csak egész számokat fogadok el.'
end

Mint látjuk, a program nagyon emlékeztet egy REXX-ben írt programra, s gyakorlatilag nincs benne semmi olyan, amely elárulná azt, hogy ezt egy objektum-orientált nyelven írták. A szkript elsô részében képezünk egy 0 és 1000 közötti egész számot a Math.random függvény (metódus) segítségével. Ezt követôen egy hurokban folytatódik a program, amely egészen addig tart, amíg a felhasználó el nem találja a számot, vagy amíg érvénytelen adattal próbálkozik. Sikertelen tipp esetén tájékoztatjuk a felhasználót, hogy kisebb, vagy nagyobb-e a keresett szám a tippeltnél.

Figyeljük meg a say count'. tipp? \-' sor végén található \- hatását a program futtatása során! Amint látható, a speciális lezárás hatására kiíratáskor a cursor nem ugrik vissza a következô sor elejére, hanem a megjelenített karakterlánc végén várakozik. Ugyanilyen hatása van a \0 lezárásnak.

Függvények és eljárások

Kicsit bonyolultabb programok esetén mindig felvetôdik a strukturált programozás igénye, amelyet függvények vagy eljárások segítségével lehet megvalósítani. Mint tudjuk, a függvény és az eljárás között az a különbség, hogy a függvény értéket ad vissza, míg az eljárás nem. A NetREXX-ben a függvényeket és eljárásokat metódusként definiáljuk. Amennyiben a metódusnak van visszatérési értéke, akkor függvényrôl, amennyiben nincs, akkor pedig eljárásról beszélünk. A szkriptekben használt metódusok definiálásakor meg kell adni a static kulcsszót, ugyanis ellenkezô esetben a metódus nem lesz elérhetô a szkript számára, és a fordítóprogram nem ismert metódusra fog panaszkodni annak használata esetén:

method név([paraméterlista]) static

A paraméterlistában kell megadni mindazon paramétereket, amelyekkel a metódust akarjuk meghívni. Amennyiben nincsenek ilyen paraméterek, akkor a zárójeleket akár el is hagyhatjuk. A metódusok a return utasítás segítségével adhatnak vissza értékeket. Eljárások esetében a return utasítást csak paraméter nélkül szabad használni. Lényeges különbség a klasszikus REXX-hez képest, hogy globális változók itt nem léteznek. Ez azt jelenti, hogy a metódusok csak azokkal az adatokkal dolgozhatnak, amelyeket a paraméterlistán keresztül adunk meg. Ennyi tudással a birtokunkban már biztosan megértjük a játékprogram strukturáltabb változatát:

/* játék 2 */

say 'Választok egy számot 0 és 1000 között.'
number = 1000*Math.random() % 1 -- %1 egész számmá alakítjuk
say 'Kész vagyok!'
guess = int

loop count = 1 until guess = number
 	say count'. tipp? \-'
 	guess = getNumber() -- meghívunk egy függvényt
 	showAnswer(guess,number,count) -- meghívunk egy eljárást
end

method showAnswer(guess,number,count) static

 	select
 		when guess > number then
 			say 'A(z) 'guess' túl nagy.'
 		when guess < number then
 			say 'A(z) 'guess' túl kicsi.'
 		otherwise
 			say
 			say 'Gratulálok! A kérdést' count 'próbálkozás után oldotta meg.'
 	end

method getNumber static returns int

 	loop forever
 		number = ask
 		if number.datatype("W") then return number 
 		say "Sajnos a megadott adat ("number") nem érvényes!"
 		say "Adjon meg új adatot! \-"
 	end

Amint látható, a tipp bekérését egy függvény (getNumber), a tipp kiértékelését pedig egy eljárás (showAnswer) formájában oldottuk meg.

Külsô metódusok használata

A fenti példában a metódusok és a program, amelybôl meghívtuk ôket, ugyanazon fájlon belül helyezkedtek el. Természetesen a NetREXX programokból meghívhatunk olyan metódusokat is, amelyek más fájlokban találhatók. Ebben az esetben külsô metódusokról beszélünk. Amennyiben a külsô metódust tartalmazó fájl ugyanabban a könyvtárban található, mint a program, amelybôl meghívjuk, akkor nagyon könnyû dolgunk van. Ebben az esetben ugyanis a tartalmazó fájl nevét kell a metódus meghívásakor annak neve elôtt megadni. Ha pl. a játékprogram getNumber függvénye az input.nrx fájlban lenne található, akkor a metódusra a következô módon hivatkozhatnánk:

guess = Input.getNumber()

Nehezebb dolgunk van, ha a metódus fájlja nem a fôprogrammal megegyezô könyvtárban van. Ekkor ugyanis ún. csomagot (package) kell készítenünk. A csomagkészítés receptje a következô:

Ezzel kész is a csomag. Hogy a szkriptek használni tudják a csomagban található metódusokat, elôbb importálniuk kell a csomagot az import [csomagnév] utasítással, amelynek a program elején kell szerepelnie.

Nem Java programok használata

A Java fejlesztôkörnyezet segítségével elindíthatunk nem Java programokat is a NetREXX szkriptekbôl. Erre a Runtime osztály exec metódusát használhatjuk fel:

Runtime.getRuntime().exec(program)

Mint látjuk, az exec metódus használata elôtt le kell kérdezni a jelenlegi Runtime objektumot a getRuntime hívással. Sikertelen programindítás esetén IOException történik. Az elindított programmal az exec metódus által visszaadott Process objektum metódusaival kommunikálhatunk. Az alábbi táblázatban összefoglaltuk a használható metódusokat:

Metódus:Feladat:
getErrorStreamVisszaad egy InputStreamet, amely az objektum standard error streamjéhez csatlakozik.
getInputStreamVisszaad egy InputStreamet, amely az objektum standard output streamjéhez csatlakozik.
getOutputstreamVisszaad egy OutputStreamet, amely az objektum standard input streamjéhez csatlakozik.
destroyA program lelövése.
exitValueA program által visszaadott érték lekérdezése. A program végrehajtásának befejezése elôtt meghívva IllegalThreadStateException hiba történik.
waitForVár, amíg a program futása leáll.

A jobb érthetôség kedvéért most nézzünk meg egy példát, amely az UNZIP program NetREXX programból történô használatát demonstrálja:

/* Az unzip használata NetREXX-bôl */

parse arg unzip zipfile .

do
 	say "A" zipfile" fájlban található fájlok neve, mérete és dátuma:"
	say 
 	child = Runtime.getRuntime().exec(unzip ' -v' zipfile)

 	-- olvassuk az elindított folyamat output streamjét
 	in = BufferedReader(InputStreamReader(child.getInputStream()))
 	line = in.readline
 	start = 0 -- a fájlok listája még nem elérhetô

 	loop while line \= null -- amíg van adat, addig megy a feldolgozás
 		parse line l . . . d . . n
 		if l = ' ------' then start = \start
 		else if start then say n l d
		line = in.readline()
	end

 	-- megvárjuk a folyamat leállását és lekérdezzük a visszatérési értékét
 	child.waitFor()
 	if child.exitValue() \= 0 then
 		say 'Az unzip visszatérési értéke:' child.exitValue()
 	catch IOException
 	say 'Nem találom a(z)' unzip 'programot.'
 	catch e2=InterruptedException
 	e2.printStackTrace()

end

A program elsô soraiban kérjük be az unzip programot és a vizsgálandó zip fájlt. Mivel kezelni akarjuk a váratlan eseményeket a catch utasítással, ezért do-end struktúrába ágyazzuk be a program négy fô részre oszló folytatását. Az elsô részben elindítjuk a megfelelôen paraméterezett unzip programot az exec metódussal. A második részben rácsatlakozunk az elindított program kimenetét közvetítô csatornára (standard output), és inicializáljuk a kimenetre küldött sor (line) és a fájllista megjelenését jelzô változó (start) alapértékét. A harmadik részben található hurokban egészen addig tartózkodunk, amíg az unzip kimenetén adat van. A kimeneten megjelenô sorokból a parse utasítással szûrjük ki a zip fájlban található fájlok méretét, dátumát és nevét. A start változó 0 vagy nem 0 értéke jelzi, hogy feldolgozandó adatsorral van-e dolgunk. A start értékét az l (length) változó értéke állítja, ugyanis a fájlok listáját a '------' karakterlánc nyitja, illetve zárja. A negyedig részben figyeljük az unzip program bezárulását és nem 0 visszatérési érték esetén informáljuk a felhasználót. Itt kapott helyet a váratlan események (pl. hiányzó unzip program) lekezelésére szolgáló két catch utasítás is. Mivel a példaprogram parancssorban megadott paraméterekkel dolgozik, ezért két lépésben kell futtatni. A NetREXX fordítóval elôször el kell készíttetnünk a Java bájtkódot (nrc lecke03c), majd pedig ezt kell futtatni a megfelelô paraméterekkel (java lecke03c unzip.exe [zip-fájl]).

Mi történik a színfalak mögött?

Mivel a Java nyelv nem igazán alkalmas szkriptek írására, ezért a NetREXX környezetnek el kell végeznie néhány átalakítást az általunk írt szkripten, mielôtt az futtatható lesz a Java környezetben. Amikor egy Java programot futtatunk, akkor tulajdonképpen az elindított osztály fô metódusa kerül meghívásra. A fô metódusnak static-nek kell lennie, mivel az osztályhoz tartozik, és nem pedig az abból képzett objektumokhoz. A szkriptekben nem definiálunk osztályokat és fô metódusokat, ezért a NetREXX a fájl neve alapján készít egy osztályt, s fô metódusként a programunkat képezô kódot használja. Amennyiben programunkon belül függvényeket vagy eljárásokat definiáltunk, akkor ezeket az osztály további metódusaiként értelmezi.

Az elôzô példaprogramban láttuk, hogy a parse arg utasítás segítségével tudtuk feldolgozni a parancssorban megadott paramétereket. A NetREXX természetesen automatikusan befûzte programunk sorai elé az osztály és a fô metódus definícióját. Mi magunk is definiálhatjuk a fô metódust, és ekkor közvetlenül is használhatjuk a paramétereket tartalmazó tömböt, illetve a paraméterek számát tartalmazó length tulajdonságot is:

class valami
	method main(args=String[]) static
 		say "Paraméterek száma:" args.length
 		loop i=0 to args.length-1
 		say " - Az argument száma: "i", értéke: "args[i]
 	end

	...

Ha nem használjuk a paramétereket, akkor a NetREXX figyelmeztetô üzenetet fog küldeni a fordításkor, ezért érdemes egy ál sort is beiktatni a fô metódus definiálása után:

method main(args=String[]) static
 	args = args


REXX GYÍK:

K1. A Java programok egyik elônye a széleskörû felhasználhatóság. Rendszerspecifikus programok beágyazásával ez az elôny elvész. Vagy mégsem?
V1. De igen. Éppen ezért bánjunk nagyon csínján ezzel a lehetôséggel, s operációsrendszer-függô programokat csak akkor ágyazzunk be programunkba, ha erre feltétlenül szükség van!


Gyakorlatok:

1. Készítsen egy tetszôleges szkriptet, amely külsô metódusokat használ!

2. Készítsen egy tetszôleges szkriptet, amely rendszerspecifikus programokat hív meg!

Kádár Zsolt
1999. 12. 29.
[ Elôzô lecke | Következô lecke | Tartalom ]