A Lua programozási nyelv

Tcp/Ip

A Lua egy beágyazott nyelv, nem önálló alkalmazásként való futtatásra tervezték, hanem csatolható könyvtárként, egy kiszolgáló (host) alkalmazásba integrálásra.

Az eddigi leírásokban bemutatott lua interpreter is egy ilyen kiszolgáló alkalmazás. Maga az interpreter tulajdonképpen egy párszáz soros C alkalmazás, ami beolvassa a felhasználói bemeneteket, fájlokat és stringeket, és ezt továbbadja a Lua könyvtárnak.

A könyvtárként való használat az, ami kiegészítő nyelvvé teszi a Lua-t. Emellett a Lua-t használó programoknak lehetőségük nyílik új függvényeket hozzáadni a Lua környezethez. Ezeket a függvényeket C-ben implementálhatjuk, vagy akár más C kompatibilis nyelvben (például Pascalban), és segítségükkel olyan lehetőségeket adhatunk a nyelvhez amit Lua kódban nem lehetne megvalósítani. Ez a Lua nyelvet bővíthetővé teszi.

Ez a két nézete a nyelvnek (a kiegészítő és bővíthető nyelv) két különböző C - Lua interakciót kíván. Az első nézet során a C kód irányítja a Lua könyvtárat és hívja meg az egyes függvényeit. A C kódot ebben az interakcióban alkalmazás kódnak hívjuk. A második nézet során a Lua környezet irányítja a C kódot, ilyenkor a C kódot könyvtári kódnak hívjuk. Mind az alkalmazás, mind a könyvtári kód ugyanazt az API-t hívja a Lua környezettel való kommunikáláshoz. Ezt a Lua C API-nak hívjuk.

Ez a fejezet a Lua C API-t mutatja be, ami kulcsfontosságú szerepet töltött be a Lua sikerességében. A továbbiak megértéséhez feltételezzük, hogy az olvasó legalább alapszintű ismeretekkel rendelkezik a C nyelvről.

Egyszerű interpreter

Elsőnek egy egyszerű Lua interpreteren keresztül mutatjuk be a Lua C API használatát.

#include <stdio.h> #include <string.h> #include <lua.h> #include <lauxlib.h> #include <lualib.h> int main (void) { char buff[256]; int error; lua_State *L = luaL_newstate(); /* letrehozunk egy Lua stacket */ luaL_openlibs(L); /* betoltjuk az alap konyvtarakat */ while (fgets(buff, sizeof(buff), stdin) != NULL) { error = luaL_loadbuffer(L, buff, strlen(buff), "line") || lua_pcall(L, 0, 0, 0); if (error) { fprintf(stderr, "%s", lua_tostring(L, -1)); lua_pop(L, 1); /* levesszuk a hibauzenetet a stackrol */ } } lua_close(L); return 0; }

A fenti C kód először include-olja megfelelő header fájlokat a Lua-ból. A lua.h definiálja az alapvető Lua függvényeket. Ezekkel a függvényekkel meghívhatunk Lua kódokat (lua_pcall), módosíthatunk globális változókat, regisztrálhatunk új függvényeket, stb. A lauxlib.h header fájl a kiegészítő könyvtár függvényeit definiálja. Ezek egy magasabb szintű absztrakciót adnak a lua.h függvényeit felhasználva. A kiegészítő könyvtár függvényei mindig luaL_ előtaggal kezdődnek, az alap könyvtár függvényei pedig lua_ előtaggal.
A harmadik, lualib.h header fájlban lévő függvények felelősek a Lua környezet könyvtárainak betöltéséért. Ezek regisztrálják a globális névtérből alapértelmezésben elérhető print függvényt, io táblát, stb. Amennyiben nem akarunk a futtatott Lua kódnak minden könyvtárhoz hozzáférést adni betölthetjük őket külön-külön is a luaopen_base, luaopen_table, stb. függvények segítségével.

A luaL_newstate() függvény segítségével létrehozunk egy Lua futtató környezetet, ami Lua futtatásához szükséges összes adatot tárolni fogja és a Lua C API minden függvényének át kell adni. Ezután betöltjük az alap Lua könyvtárakat, hogy egy teljes Lua környezetet tudjunk biztosítani.

A környezet inicializálása után felhasználói bemenetet várunk. Minden beírt sort betöltünk a Lua verembe a luaL_loadbuffer függvénnyel majd meghívjuk a Lua környezetet védett módban a lua_pcall függvénnyel. Amennyiben hibát jelezett valamelyik, akkor kiírjuk a hibaüzenetet és töröljük a veremből.

Vegyük észre, hogy a hibaüzenetet a program a standard error kimenetre írja. A hibakezelés elég összetett is lehet a C nyelvben. A Lua függvények nem írnak közvetlenül semmilyen kimenetre, helyette a hibákat visszatérési kódokkal/üzenetekkel jelzi. A használó alkalmazások ezeket a számukra legmegfelelőbb módon kezelhetik. Később kitérünk majd a hibakezelés megvalósítására az alkalmazás kódban.

A Lua-t fordíthatjuk C és C++ fordítóval is, de a lua.h nem tartalmazza a C könyvtárakban már megszokott makrót:

#ifdef __cplusplus extern "C" { #endif ... #ifdef __cplusplus } #endif

Ezért, ha C++ fordítóval fordíjuk a kódunkat, használjuk a lua.hpp header fájlt.

A Verem

A Lua és a C nyelv közötti adatcserénél két nyelv eltérő típusrendszere és memória foglalási stratégiája miatt problémákba ütközünk. Ezeket az adatcsere problémákat a Lua C API egy absztrakt verem használatával oldja meg. A verem rekeszekre van osztva, minden rekesz egy tetszőleges Lua értéket tartalmazhat. Ha szükségünk van a Lua környezet valamely értékére, akkor a megfelelő hívással a Lua környezetet utasíthatjuk, hogy rakja a verembe. Hasonlóan, ha egy Lua kódnak akarunk átadni értékeket, akkor a megfelelő push függvény meghívásával a Lua ezt a verembe teszi. Szinte az összes API függvény a vermet használja.

A Lua környezet ezt a vermet LIFO eléréssel használja (Last In, First Out), amikor meghívjuk a Luat az mindig csak a verem tetejét változtatja. A C kódnak ennél nagyobb szabadsága van a veremkezelésben, tetszőleges helyen olvashat vagy módosíthat elemeket.

Elemek hozzáadása a veremhez

Az API minden típushoz biztosít lua_push* függvényeket, amikkel a verem tetejére helyezhetjük a megfelelő C típusú értékeket. A lua_pushnil függvény nil értéket, a lua_pushnumber egy double típusú számot, a lua_pushboolean egy logikai értéket, a lua_pushlstring tetszőleges stringet, a lua_pushstring pedig null terminált stringet rak a verem tetejére.

void lua_pushboolean (lua_State *L, int b); void lua_pushlstring (lua_State *L, const char *s, size_t len); void lua_pushstring (lua_State *L, const char *s); void lua_pushnil (lua_State *L); void lua_pushnumber (lua_State *L, double n);
Bővebb felsorolás a Lua Manual-ban.

A verem kezelése során a C kód felelős a verem méretének kezeléséért. Lua indításakor, és bármikor amikor a Lua a C nyelvi függvényeket hívja garantálja, hogy legalább 20 szabad rekesz elérhető lesz a verem tetején. Amennyiben ez nem elég a C kód számára a következő függvénnyel beállíthatjuk a kívánt stack méretet.

int lua_checkstack (lua_State *L, int extra);

Elemek lekérdezése a veremből

A Lua verem egyes rekeszeinek eléréshez az API indexeket használ. A verem első eleme (amelyik legelőször került a verembe) az 1 -es indexen található, a következő elem a 2 -es indexen, és így tovább. A verem tetején lévő, utoljára berakott elemeket negatív indexekkel érhetjük el egyszerűen. Negatív indexek esetén a -1 -es index a verem legtetején lévő (legutoljára bekerült) elemet éri el, a -2 az előtte bekerült elemet, és így tovább. Például a lua_tonumber(L, -1) a verem tetején lévő számot adja vissza.

A verem adott rekeszének típusát a lua_is* függvényekkel kérdezhetjük le:

int lua_isnumber (lua_State *L, int index); int lua_isstring (lua_State *L, int index); ...
Bővebb felsorolás a Lua Manual-ban.

Ezek a függvények 1 -et adnak vissza, ha az adott verem rekeszen megfelelő típusú érték van.



A verem egy rekeszének értékét a lua_to* függvényekkel kérdezhetjük le:

const char *lua_tostring (lua_State *L, int index); const char *lua_tolstring (lua_State *L, int index, size_t *len); double lua_tonumber (lua_State *L, int index); int lua_toboolean (lua_State *L, int index); ...
Bővebb felsorolás a Lua Manual-ban.

A lua_tonumber és lua_toboolean függvények hiba esetén 0-t adnak vissza, a mutatóval visszatérő függvények pedig NULL-t adnak hiba esetén. A lua_is* típus ellenőrzésre többek között azért van szükség, mert némely típusnál (például number-nél) a hiba esetén visszaadott 0 értékről nem lehet nélkülük eldönteni, hogy valós érték-e, vagy hibajelzés.
Verem használat, példakód:

luaL_dostring(L,"return 'Hello' .. ' World'"); /* lefuttatjuk a stringben atadaott Lua kodot */ const char* s = lua_tostring(L, -1); /* lekerjuk a verem legfelso elemet */ if(s) { printf("from Lua: %s\n", s); lua_pop(L, 1); /* eltavolitjuk a verem legfelso elemet */ }

Egyéb verem műveletek

int lua_gettop (lua_State *L);A verem tetején lévő elem indexét adja vissza
void lua_settop (lua_State *L, int index);Beállítja a verem tetejét a megfelelő indexre
void lua_pushvalue (lua_State *L, int index);Az adott indexen lévő elemet a verem tetejére másolja
void lua_remove (lua_State *L, int index);Az adott indexű elemet kiveszi a veremből és a többi elemet lejjebb csúsztatja
void lua_insert (lua_State *L, int index);Az adott indexre áthelyezi a legfelső elemet és a többi elemet feljebb csúsztatja
void lua_replace (lua_State *L, int index);A felső elemet megcseréli az adott indexen lévővel, csúsztatás nélkül

A lua_gettop függvény a veremben található elemek számával tér vissza, ami egyben a legfelső elem indexe is. A lua_settop fügvénnyel tudjuk a verem tetejére mutató indexet beállítani. Ha az előző index nagyobb volt mint az új, akkor a fölösleges elemeket eldobjuk, ha viszont kisebb volt, a függvény nil-ekkel tölti fel az üres helyeket. Ha például ki akarjuk üríteni a vermet, használhatjuk a lua_settop(L,0) függvényt. Negatív számokat is megadhatunk paraméternek. Például az API által nyújtott lua_pop függvény megvalósítása:

#define lua_pop(L,n) lua_settop(L, -(n) - 1)

A lua_pushvalue függvény a veremnek a paraméterben megadott indexű elemének a másolatát a verem tetejére másolja. A lua_remove eltávolítja a paraméterben megadott indexű elemet a veremből és ha az adott index felett találhatóak még elemek, akkor azokat lecsúsztatja eggyel. A lua_insert a verem tetején lévő elem másolatát a megadott indexre beszúrja, úgy hogy előtte elcsúsztatja a veremben az index felett lévő elemeket eggyel. A lua_replace a verem legfelső elemét az adott indexre másolja. Vegyük észre, hogy a következő műveleteknek semmilyen hatása nincs a virtuális vermen:

lua_settop(L, -1); /* A verem tetejét jelző indexet az aktuális értékre állítja */ lua_insert(L, -1); /* A verem legfelső elemének a verem tetejére mozgatása */

Alkalmazás Kód

A C nyelvű alkalmazások kiegészítésekor a Lua-t az alkalmazás működését kiegészítő scriptek írására és konfigurációs fájlok nyelveként is használják.

Nézzünk egy példát egy szerver alkalmazás pár beállításának betöltésére:

-- a szerver program konfiguracios fajlja -- megadjuk a portot port = 123 -- az eth0 interfacen kell létrehozni a szervert interface = "eth0" maxusers = 50

Ezt a fájlt közvetlenül átadhatjuk a Lua-nak és a fájl végrehajtása után kiolvashatjuk a globális változók értékét:

void loadconfig(const char* filename) { int port, maxusers; const char* interface; lua_State *L = luaL_newstate(); luaL_openlibs(L); if(luaL_dofile(L, filename)) { printf("Hiba: %s\n", lua_tostring(L, -1)); } else { lua_getglobal(L, "port"); lua_getglobal(L, "interface"); lua_getglobal(L, "maxusers"); if(lua_isnumber(L, -3) && lua_isstring(L, -2) && lua_isnumber(L, -1)) { port=lua_tonumber(L, -3); interface=lua_tostring(L, -2); maxusers=lua_tonumber(L, -1); printf("Sikeres betoltes! port: %d, interface: %s, maxusers: %d\n", port, interface, maxusers); } else printf("Hibas ertekek\n"); } lua_close(L); }

A fenti kód létrehoz egy új Lua környezetet majd betölti a standard Lua könyvtárakat és lefuttatja a megadott fájlt a luaL_dofile függvénnyel. Miután a kódrészlet befejezte a futást lekéri a megfelelő globális változókat a verembe és ellenőrzi, hogy jó-e a típusuk. Ha megfelel a típus, akkor kiolvassa őket és kiírja.

Táblák kezelése

A Lua táblák mezőit a C nyelvből a megfelelő tábla és kulcs verembe helyezésével tudjuk elérni. A következő kódrészlet betölti a person tábla age mezőjét, a lua_gettable függvényt használva:

lua_getglobal(L, "person"); /* betoltjuk a verembe a person globális változót */ lua_pushstring(L, "age"); /* betoltjuk a verembe az "age" kulcsot */ lua_gettable(L, -2); /* person["age"] indexelés */ int age = lua_tonumber(L, -1); /* lekérjük a verem tetejét */ lua_pop(L, 1); /* töröljük a visszakapott értéket a veremből */

Függvények hívása

A Lua függvények hívása a többi művelethez hasonlóan a vermen keresztül történik. Először a függvény objektum kerül a veremre, majd a paraméterei. A függvény hívást a lua_pcall fügvénnyel végezhetjük el, ez kiveszi a veremből a függvényt és a paramétereket, majd a verem tetejére rakja a visszatérési értékeket.

lua_getglobal(L, "f"); lua_pushnumber(L, 5); lua_pushnumber(L, x); /* 2 parameter, 1 visszateresi ertek */ if(lua_pcall(L, 2, 1, 0)!=0) printf("Hiba: %s\n", lua_tostring(L, -1)); else { int res = lua_tonumber(L, -1) lua_pop(L, 1); }

Könyvtári Kód

A Lua képes meghívni megfelelő prototípusú és működésű C függvényeket, amiket a alkalmazás kód regisztrált a Lua környezetbe. A meghívott C függvény a Lua vermen kapja meg a paramétereit és az eredményeket is vermen adja vissza. Minden hívott C függvény egy privát Lua vermet kap, ahol mindig az 1 -es indextől kezdődnek a paraméterek.

A lenti kódrészlet egy Luaból hívható C függvényt implementál, az egyszerűség kedvéért ez egy 1 paraméteres függvény lesz, ami a paraméter szinuszát adja vissza. Ezt a függvényt a lua_pushcfunction és lua_setglobal függvény segítségével regisztrálja.

static int myfunc (lua_State *L) { double d = lua_tonumber(L, 1); /* lekerjuk a paramtert */ lua_pushnumber(L, sin(d)); /* berakjuk a verembe az erdmenyt */ return 1; /* visszaadjuk az eremenyek szamat */ } void register() { lua_pushcfunction(l, myfunc); /* berakjuk a fuggvenyt a verembe */ lua_setglobal(l, "mysin"); /* hozzarendeljuk a megfelelo globalis valtozohoz */ }

A függvények regisztrálására egy elegánsabb módszer ha a standard könyvtárokhoz hasonló táblába töltjük be a függvényeinket. Ehhez egy a következő kódot használhatjuk:

static const struct luaL_reg mylib [] = { {"mysin", myfunc}, /* fuggvenynev, fuggveny mutato */ {NULL, NULL} /* konyvtar vege */ }; void register() { luaL_openlib(L, "mylib", mylib, 0); /* regisztraljuk a fuggveny tombot */ }

Hibakezelés a C API segítségével

Ellentétben a C++-al vagy a Java-val, a C nyelv nem nyújt támogatást a hibakezeléshez. Ezt kiküszöbölendő a Lua a C setjmp képességét használja, amivel a kivételkezeléshez hasonló működést valósít meg.

Minden struktúra Lua-ban dinamikus, azaz növekszik ha szükség van rá, vagy csökken ha lehetséges. Ez azt jelenti, hogy a memória foglalási hibák kiterjednek a Lua-ra is. Ahelyett hogy hibakódokat használna a Lua minden műveletnél, inkább kivételeket használ, hogy jelezze a hibákat. Ez azt jelent, hogy majdnem az összes API függvény dobhat hibát, ahelyett hogy visszatérne.

Amikor alkalmazás kódot írunk (ahol a C hívja a Lua-t), a mi felelősségünk hogy elkapjuk ezeket a hibákat.

Hibakezelés alkalmazás kódban

Jellemzően az alkalmazás kód unprotected módon fut. Mivel a Lua az alkalmazás kódhoz nem fér hozzá, így elkapni sem tudja a kivételeket. Ilyenkor, ha például egy művelet memóriát akar foglalni és nincs elég hely a Lua nem tudja elkapni ezt a hibaüzenetet. Helyette egy pánik függvényt hív és ha ez visszatér az alkalmazás terminál. Megadhatunk saját pánik függvényt a lua_atpanic segítségével.

Nem minden API függvény dob kivételt. A luaL_newstate, lua_load, lua_pcall és lua_close függvények biztonságosak. A legtöbb függvény csak akkor dob kivételt, ha nincs elég hely a memóriában allokáció során például a luaL_loadfile kivételt dob ha nincs elég memória egy fájl nevének tárolására. A legtöbb program nem tud mit tenni, ha kifogy a memóriából, így figyelmen kívül hagyható ez a kivétel.

Ha azt szeretnénk, hogy ne termináljon a programunk memória allokációs hiba esetén sem két lehetőségünk van. Az első, hogy beállítunk egy pánik függvényt ami nem adja vissza a Lua-nak a vezérlést. A második, hogy védett(protected) módban futtatjuk a programunk.

A legtöbb alkalmazás a lua_pacall függvényhíváson keresztül futtat Lua kódot, ezért a Lua kód védett módban fut. Ha memória allokációs hiba keletkezik a lua_pcall egy hibakóddal tér vissza így az interpretert egy konzisztens állapotban hagyja. Ha szeretnénk az alkalmazás kódunkat is biztonságossá tenni, akkor a lua_cpcall függvényt hívjuk meg. Ez ugyanúgy működik mint a lua_pcall, de paraméterként kap egy C függvényt, ami akkor hívódik meg ha memória foglalási hiba történt miközben egy Lua függvényt akartunk a verembe tenni.

Az alkalmazásunk kibővítése

Egy fontos alkalmazási területe a Luának a konfigurációs nyelvként való használata. Ebben a fejezetben bemutatjuk hogyan használhatjuk a Lua-t arra, hogy egy programot konfiguráljunk. Először egy egyszerű példával kezdünk, majd ezt egészítjük ki sokkal komplexebb feladatok megoldására alkalmas módon.

Az alapok

Először is képzeljünk el, egy egszerű konfigurációs feladatot: a programunk egy ablakot jelenít meg és azt akarjuk, hogy a felhasználó tudja állítani a kezdeti méretét. Persze ilyen egyszerű feladatot egyszerűbben is meg lehet oldani, nem szükséges Lua-t használni hozzá. Mi most Lua-t fogunk használni, ekkor a konfigurációs fájlunk a következő.

-- define window size width = 200 height = 300

Most a Lua-t arra kell utasítanunk, hogy olvassa be a fájlt és elemezze, majd a globális width és height változókat el kell kérnünk tőle. Ezt a load függvénnyel tudjuk megtenni. Ha létrehoztunk már egy lua state-et, akkor a luaL_loadfile függvénnyel tudunk egy fájlt betölteni a Lua-ba, majd hívhatjuk a lua_pcall függvényt. Ha hiba történt az elemzés során például szintaktikai hiba volt a fájlban, akkor az előbbi függvények egy hibakóddal térnek vissza amit a virtuális verembe tesznek. Ezután a programunkban a lua_tostring függvényt a -1 paraméterrel hívva megkaphatjuk a verem tetején lévő hibaüzenetet.

void load (lua_State *L, const char *fname, int *w, int *h) { if (luaL_loadfile(L, fname) || lua_pcall(L, 0, 0, 0)) error(L, "cannot run config. file: %s", lua_tostring(L, -1)); lua_getglobal(L, "width"); lua_getglobal(L, "height"); if (!lua_isnumber(L, -2)) error(L, "’width’ should be a number\n"); if (!lua_isnumber(L, -1)) error(L, "’height’ should be a number\n"); *w = lua_tointeger(L, -2); *h = lua_tointeger(L, -1); }

Ahhoz, hogy megkapjuk a globális változókat kétszer meghívjuk a lua_getglobal függvényt aminek paraméterként a változó nevét adjuk meg. Ezzel a verembe kerül a két változónk a -2 indexen a width a height pedig a -1-esen. Ezután megvizsgáljuk, hogy a veremben lévő érték az szám típusú-e lua_isnumber. Ha igen akkor meghívjuk a lua_tointeger függvényt, hogy átkonvertáljuk az értékeket, majd a megfelelő változókban eltároljuk őket.

Tényleg megéri Lua-t használni ilyen egyszerű feladatokra? Ahogyan az előzőekben már említettük, ilyen egyszerű feladatokra egyszerűbb lenne csak beolvasni két számot egy szöveges fájlból. Viszont a Lua használatának megvan az az előnye, hogy kezeli a szintaktikus hibákat helyettünk, a kommenteket, valamint sokkal összetettebb konfigurációkat is megadhatunk. Például bekérhet a felhasználótól adatokat, vagy rendszerváltozókból olvashat be értékeket:

-- configuration file if getenv("DISPLAY") == ":0.0" then width = 300; height = 300 else width = 200; height = 200 end

Még ilyen egyszerű esetekben is nehéz mérlegelni, hogy mit akar a felhasználó, de amíg ez a két változó definiált a scriptben addig a C alkalmazásunk változtatás nélkül működni fog.

Végül nem elhanyagolható érv a Lua használata mellett az sem, hogy könnyű új konfigurációs fájlokat hozzáadni a meglévő programunkhoz, ami sokkal flexibilisebbé teszi a programunkat.

Táblák kezelése

Tegyük fel, hogy most az alkalmazásunk háttérszínét szeretnénk konfigurációs fájlból beolvasni. A színeket három számmal reprezentáljuk, ahol minden szám egy szín komponenst jelent RGB-ben. C-ben ezeket a számokat integer-rel ábrázoljuk [0,255], mivel Lua-ban minden szám valós típusú ezért használjuk a [0,1] tartományt.

Egy naív megközelítése a feladatnak az, hogy elvárjuk hogy a konfigurációs fájlban a szín komponensek külön globális változóban legyenek megadva:

-- configuration file width = 200 height = 300 background_red = 0.30 background_green = 0.10 background_blue = 0

Ennek a megközelítésnek két hátránya is van, az egyik, hogy az alkalmazásunknak több színre lehet szüksége így rengeteg globális változóra lenne szükség, a másik, hogy így a felhasználó nem tud gyakran használt színeket előre definiálni (background=WHITE). Ahhoz hogy ezt elkerüljük, táblákat fogunk használni a színek reprezentálására:

background = {r=0.30, g=0.10, b=0}

A táblák használata strukturáltabbá teszi a scriptünket, így kényelmesebb lesz később használni az előre definiált színeket:

BLUE = {r=0, g=0, b=1} ...other color definitions... background = BLUE

Hogy ezeket az értékeket kiolvassuk a C kódunkban a következőket kell tennünk:

lua_getglobal(L, "background"); if (!lua_istable(L, -1)) error(L, "’background’ is not a table"); red = getfield(L, "r"); green = getfield(L, "g"); blue = getfield(L, "b");

Először lekérjük a background globális változót, majd megvizsgáljuk, hogy az egy tábla-e. Ezután lekérjük a táblában lévő színkomponenseket. Mivel a getfield függvény nem az API része ezért definiálnunk kell.

#define MAX_COLOR 255 /* assume that table is on the stack top */ int getfield (lua_State *L, const char *key) { int result; lua_pushstring(L, key); lua_gettable(L, -2); /* get background[key] */ if (!lua_isnumber(L, -1)) error(L, "invalid component in background color"); result = (int)lua_tonumber(L, -1) * MAX_COLOR; lua_pop(L, 1); /* remove number */ return result; }

A Lua API egy függvényt lua_gettable biztosít az összes típushoz. Ez a függvény leveszi a verem tetejéről a kulcsot, majd ez alapján visszateszi a táblában található értéket a verembe. Az általunk definiált getfield feltételezi, hogy a tábla a virtuális verem tetején van, így miután letettük a kulcsot a verembe a lua_pushstring függvénnyel a tábla a -2-es indexen lesz a veremben. Mielőtt a függvény visszatérne, leveszi a verem tetején található értéket, így a vermet ugyanúgy hagyja ahogyan megkapta.

Mivel a táblák stringekkel való indexelése nagyon gyakori, ezért a Lua 5.1-ben bevezették a lua_gettable függvény egy speciális változatát erre az esetre: lua_getfield. Ezt a függvényt felhasználva lecserélhetjük ezt:

lua_pushstring(L, key); lua_gettable(L, -2); /* get background[key] */

erre:

lua_getfield(L, -1, key);

Tovább fogjuk bővíteni az alkalmazásunkat és bevezetjük a színek neveit. A felhasználók továbbra is használhatják a színtáblákat, de használhatnak előre definiált színeket is a gyakori színekhez. Ehhez a lehetőséghez implementálnunk kell egy színtáblát a C programunkban:

struct ColorTable { char *name; unsigned char red, green, blue; } colortable[] = { {"WHITE", MAX_COLOR, MAX_COLOR, MAX_COLOR}, {"RED", MAX_COLOR, 0, 0}, {"GREEN", 0, MAX_COLOR, 0}, {"BLUE", 0, 0, MAX_COLOR}, ...other colors... {NULL, 0, 0, 0} /* sentinel */ };

Ahhoz, hogy egy tábla mezőinek az értékeit beállítsuk definiálhatjuk a következő függvényt, ami leteszi a verembe az indexet és a mező értéket, majd meghívja a lua_settable függvényt:

/* assume that table is at the top */ void setfield (lua_State *L, const char *index, int value) { lua_pushstring(L, index); lua_pushnumber(L, (double)value/MAX_COLOR); lua_settable(L, -3); }

Mint a többi API függvény a lua_settable is több típusra működik. Megkapja a tábla indexét majd a veremből kiolvasva a kulcsot és értéket módosítja a táblát. A setfield függvény feltételezi, hogy a tábla van a verem tetején amikor meghívták.

A Lua 5.1 bevezeti a lua_settable egy specializált változatát a lua_setfield-et. Ezt az új függvényt használva átírhatjuk az előző definícióját a setfield-nek a következőre:

void setfield (lua_State *L, const char *index, int value) { lua_pushnumber(L, (double)value/MAX_COLOR); lua_setfield(L, -2, index); }

A következő setcolor függvény egy színt definiál. Létrehoz egy táblát, beállítja a mezőit, majd ezt értékül adja a megfelelő globális változónak:

void setcolor (lua_State *L, struct ColorTable *ct) { lua_newtable(L); /* creates a table */ setfield(L, "r", ct->red); /* table.r = ct->r */ setfield(L, "g", ct->green); /* table.g = ct->g */ setfield(L, "b", ct->blue); /* table.b = ct->b */ lua_setglobal(L, ct->name); /* ’name’ = table */ }

A lua_newtable függvény létrehoz egy új táblát és a verembe helyezi azt. A setfield hívások beállítják a tábla mezőit, végül a lua_setglobal kiveszi a táblát a virtuális veremből és értékül adja a megfelelő nevű globális változónak.

Az előzőekben definiált függvényekkel a következő ciklusban beállítjuk az összes színt a konfigurációs script számára:

int i = 0; while (colortable[i].name != NULL) setcolor(L, &colortable[i++]); lua_getglobal(L, "background"); if (lua_isstring(L, -1)) { /* value is a string? */ const char *name = lua_tostring(L, -1); /* get string */ int i; /* search the color table */ for (i = 0; colortable[i].name != NULL; i++) { if (strcmp(colorname, colortable[i].name) == 0) break; } if (colortable[i].name == NULL) /* string not found? */ error(L, "invalid color name (%s)", colorname); else { /* use colortable[i] */ red = colortable[i].red; green = colortable[i].green; blue = colortable[i].blue; } } else if (lua_istable(L, -1)) { red = getfield(L, "r"); green = getfield(L, "g"); blue = getfield(L, "b"); } else error(L, "invalid value for ’background’");

Vegyük észre, hogy az alkalamazás először a ciklust hajtja végre mielőtt futtatná a scriptet.

Más lehetőségünk is van a színek reprezentálására, például a scriptekben szövegként background="BLUE". Így a background lehet tábla is vagy szöveg. Ezzel az implementációval a programnak nem kell semmit csinálnia mielőtt futtatja a lua scriptet. Cserébe nehezebb egy színt meghatározni. Meg kell vizsgálni hogy a background változó string típusú-e és ha igen, akkor a színtáblából kikeresni a stringhez tartozó színt.

Melyik a legjobb választás? C programokban stringekben tárolni ehhez hasonló értékeket nem ajánlott, mert a fordító nem veszi észre az elírásokat. Lua-ban a globális változóknak nincs szüksége deklarációra, így a Lua nem fog semmilyen hibát jelezni ha a felhasználó elírta egy szín nevét. Ha például a felhasználó WITE-ot ír WHITE helyett a background változó értéke nil lesz. Semmilyen információnk nincs arról, hogy mi a hiba. Másfelől ha elgépeljük a stringeket akkor ezt az alkalmazásunkban felismerhetjük és a hibaüzenetben kiírhatjuk. Az az előnyük is megvan, hogy a felhasználó írhatja bárhogy a színek neveit "white", "WHITE" vagy "White", ha az összehasonlítás során a kis- és nagybetű érzékenységet kikapcsoljuk. Továbbá ha a Lua script kicsi és sok színt tartalmaz eléggé zavaró lehet színek százait definiálni, csak azért hogy a felhasználó csak néhányat használjon. Stringek használatával ez elkerülhető.

Lua függvények hívása

Egy nagyszerű képessége a Lua-nak, hogy a konfigurációs fájlban definiálhatunk függvényeket, amiket aztán hívhatunk az alkalmazásukból. Például írhatunk egy alkalmazást ami kirajzolja egy függvény képét és ezt a függvényt definiálhatjuk Lua-ban.

Lua függvények hívása az API-n keresztül igen egyszerű, csak le kell raknunk a virtuális verembe a függvény nevét, majd a paramétereit, aztán meghívjuk a lua_pcall függvényt, végül kiolvassuk a függvény visszatérési értékét a veremből. Tegyük fel, hogy adott a következő konfigurációs script egy függvénnyel:

function f (x, y) return (x^2 * math.sin(y))/(1 - x) end

Ki akarjuk értékelni C-ben a következő kifejezést z=f(x,y) adott x és y-ra. Feltéve, hogy már van egy nyitott Lua példányunk a következő C függvénnyel tudjuk meghívni az f Lua függvényt:

/* call a function ’f’ defined in Lua */ double f (double x, double y) { double z; /* push functions and arguments */ lua_getglobal(L, "f"); /* function to be called */ lua_pushnumber(L, x); /* push 1st argument */ lua_pushnumber(L, y); /* push 2nd argument */ /* do the call (2 arguments, 1 result) */ if (lua_pcall(L, 2, 1, 0) != 0) error(L, "error running function ’f’: %s", lua_tostring(L, -1)); /* retrieve result */ if (!lua_isnumber(L, -1)) error(L, "function ’f’ must return a number"); z = lua_tonumber(L, -1); lua_pop(L, 1); /* pop returned value */ return z; }

Az f függvényt a lua_pcall függvényhívással hajtjuk végre a második paraméterben az f függvény paramétereinek számát adjuk meg, a harmadik paraméterben a visszatérési értékek számát, az utolsó paraméterben pedig a hibakezelő függvényt adhatjuk meg. Attól függóen hogy harmadik paraméternek mit adtunk meg a Lua az üres helyeket nil-el tölti fel a veremben, vagy ha a visszatérési értékek száma több mint amennyit argumentumban megadtunk, akkor azokat eldobjuk. Mielőtt a függvény eredményét a verembe tenné a lua_pcall kiveszi onnan a függvény paramétereit és a nevét. Ha egy függvény több értékkel tér vissza az első érték kerül először a verembe, így ha pl.: három értéket ad a függvény, akkor az első érték a -3-as indexen lesz az utolsó pedig a -1-esen.

Ha hiba történik a lua_pcall hívása során, akkor a verembe egy nullától különböző hibakódot tesz, a hibaüzenettel együtt (persze ekkor is kiveszi a veremből a hívott függvény argumentumait és nevét). Mielőtt a verembe tenné a hibaüzenetet meghívja a hibakezelő függvényt ha megadtuk a negyedik paraméterben. Az átlagos hibák jelzésére a LUA_ERRRUN szolgál.

Egy általános Call függvény

Egy sokkal összetetteb példaként nézzük hogyan írhatunk egy wrappert a Lua függvényekhez a C változó argumentumlista mechanizmusát használva. A wrapper függvényünket nevezzük call_va-nak, ami megkapja a függvény nevét amit meg kell hívnia, egy stringet ami leírja az argumentumok és visszatérési értékek típusát, az argumentumokat, és végül pointerek listáját amik a visszatérési értékeket tároló változókra mutatnak. Ezzel a függvénnyel az előbbi példánk a következőre egyszerüsödik:

call_va("f", "dd>d", x, y, &z);

ahol a "dd>d" jelentése két double típusú paraméter és egy double típusú visszatérési érték. Ebben a formátum stringben a double típust a 'd', az int típust az 'i' és a string típust az 's'-el jelöljük. A paraméterek típusait a visszatérési értékek típusaitól a '>' választja el. Ha nincs visszatérési érték, akkor a '>'-t nem kötelező kiírni.

#include <stdarg.h> void call_va (const char *func, const char *sig, ...) { va_list vl; int narg, nres; /* number of arguments and results */ va_start(vl, sig); lua_getglobal(L, func); /* push function */ for (narg = 0; *sig; narg++) { /* repeat for each argument */ /* check stack space */ luaL_checkstack(L, 1, "too many arguments"); switch (*sig++) { case ’d’: /* double argument */ lua_pushnumber(L, va_arg(vl, double)); break; case ’i’: /* int argument */ lua_pushinteger(L, va_arg(vl, int)); break; case ’s’: /* string argument */ lua_pushstring(L, va_arg(vl, char *)); break; case ’>’: /* end of arguments */ goto endargs; default: error(L, "invalid option (%c)", *(sig - 1)); } } endargs: nres = strlen(sig); /* number of expected results */ /* do the call */ if (lua_pcall(L, narg, nres, 0) != 0) /* do the call */ error(L, "error calling ’%s’: %s", func, lua_tostring(L, -1)); nres = -nres; /* stack index of first result */ while (*sig) { /* repeat for each result */ switch (*sig++) { case ’d’: /* double result */ if (!lua_isnumber(L, nres)) error(L, "wrong result type"); *va_arg(vl, double *) = lua_tonumber(L, nres); break; case ’i’: /* int result */ if (!lua_isnumber(L, nres)) error(L, "wrong result type"); *va_arg(vl, int *) = lua_tointeger(L, nres); break; case ’s’: /* string result */ if (!lua_isstring(L, nres)) error(L, "wrong result type"); *va_arg(vl, const char **) = lua_tostring(L, nres); break; default: error(L, "invalid option (%c)", *(sig - 1)); } nres++; } va_end(vl); }