25 Uygulamalarınızı Genişletme

25 Uygulamalarınızı Genişletme

Lua'nın önemli bir kullanımı yapılandırma(konfigrasyon) dili şeklindedir. Bu bölümde basit bir örnek ile başlayıp daha komple görevleri gerçekleştirmek üzere gelişen bir program yapılandırması için Lua'ı nasıl kullanabileceğimizi göreceğiz.

25.1 Temel

İlk görevimiz olarak basit bir yapılandırma senaryosu düşünelim: C programınızın bir penceresi var ve kullanıcının başlangıç pencere boyutunu belirleyebilmesini istiyorsunuz. Açıkçası, bu tür basit görevler için ortam değişkenleri veya isim-değer çiftleri içeren dosyalar gibi Lua'ı kullanmaktan daha basit birkaç seçenek var. Ancak basit bir metin dosyası kullansak bile bir şekilde ayrıştırma yapmanız gerekir; bu nedenle bir Lua yapılandırma dosyası kullanmaya karar verirsiniz (yani bir Lua programı olan düz metin dosyası). En basit haliyle bu dosya devamdakine benzer satırlar içerebilir:

-- pencere boyu tanımlaması
genislik = 200
yukseklik = 300

Şimdi, bu dosyayı ayrıştırmak için Lua'ı yönlendirip sonra global değişkenler genislik ve yukseklik değerlerini elde etmek için Lua API'i kullanmalısınız. işi yapan 25.1 listesindeki yükleme fonksiyonu load. Bir önceki bölümde gördüklerimizi takip ederek hazırda yaratılmış bir Lua durumuna sahip olduğunuz varsayılmakta. fname dosyasından öbeği yüklemek için luaL_loadfile 'i çağırır ve sonra derlenmiş öbeği çalıştırmak için lua_pcall 'i çağırır. Hata durumunda (örneğin yapılandırma dosyanızdaki bir sözdizimi hatası) bu fonksiyonlar hata iletisini yığına push eder ve sıfırdan farklı bir hata kodu döndürür. Programımız sonra yığının üstünden mesajı almak için index -1 ile lua_tostring 'i kullanır. (Bölüm 24.1'de hata fonksiyonu error 'u tanımladık.)

Liste 25.1. Yapılandırma dosyasından kullanıcı bilgilerini alma:

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, "konfigrasyon yürütelemiyor. dosya: %s", lua_tostring(L, -1));
    lua_getglobal(L, "genislik");
    lua_getglobal(L, "yukseklik");
    if (!lua_isnumber(L, -2))
        error(L, "'genislik' bir sayı olmalı\n");
    if (!lua_isnumber(L, -1))
        error(L, "'yukseklik' bir sayı olmalı\n");
    *w = lua_tointeger(L, -2);
    *h = lua_tointeger(L, -1);
}

öbek çalıştırıldıktan sonra program global değişkenlerin değerlerini alması gerekir. Bunun için ikinci parametresi değişken adıyla lua_getglobal iki kez çağrılır(ilk parametre lua_State, her zaman her yerde) . Her çağrı karşılık gelen global değeri yığına koyar bu şekilde genislik indeks -2'de ve yukselik indeks -1'de (üstte) olacak. (Yığın daha önce boş olduğu için ilk değer için 1 ve ikinci değer için 2 indeksinini kullanarak alttan da indeksleyebilirsiniz. Bununla birlikte üstten indeksleyerek yığın boş olmasa bile kodunuz çalışır.) Sonra, örneğimiz her bir değerin sayısal olup olmadığını kontrol etmek için lua_isnumber kullanır. Daha sonra tamsayıya bu değerleri çevirmek için lua_tointeger 'i çağırır ve kendi konumlarına onları atar. 

Bu görev için Lua kullanmaya değer mi? Daha önce de söylediğim gibi bu kadar basit görevler için
içinde sadece iki sayı bulunan basit bir dosya, Lua'ı kullanmaktan daha kolay olurdu. Yine de Lua kullanımı bazı avantajlar getirir. İlk olarak, Lua sizin için tüm sözdizimi detaylarını (ve hatalarını) ele alır; yapılandırma dosyanız yorumlara bile sahip olabilir! İkincisi, kullanıcı hazırda onunla daha karmaşık yapılandırmalar yapabilir. Örneğin script,  bazı bilgiler için kullanıcıyı yönlendirebilir veya uygun boyu seçmek için bir ortam değişkeni sorgulayabilir:

-- konfigrasyon dosyası
if getenv("DISPLAY") == ":0.0" then
    genislik= 300
    yukseklik= 300
else
    genislik= 200
    yukseklik= 200
end

Böyle basit yapılandırma senaryolarında bile kullanıcıların ne isteyeceğini tahmin etmek zordur;
ancak script iki değişkeni tanımladığı sürece C uygulamanız değişiklik yapılmaksızın çalışır.

Lua kullanmanın son bir nedeni, programınıza yeni yapılandırma imkanları eklemek artık kolay; bu kolaylık daha esnek programların ortaya çıkmasını sağlayan bir hal oluşturur.

25.2 Tablo Manipülasyonu


Bu hali benimseyerek: şimdi, pencere için arka plan rengini de yapılandırmak istiyoruz. nihai renk belirtiminin  üç sayının bileşininden oluştuğunu ki her bir sayının RGB cinsinden bir renk bileşeni olduğunu varsayıyoruz. Tipik olarak C'de bu sayılar [0, 255] şeklinde bir aralıktaki tam sayılardır. Lua'da  tüm sayılar gerçek sayılar olduğu için daha doğal aralık olan  [0, 1] 'i kullanabiliriz. 

Buradaki temel yaklaşım, kullanıcının her bir bileşeni farklı bir global değişkene set etmesini istemektir:

-- konfigrasyon dosyası
genislik= 200
yukseklik= 300
arkaplan_red = 0.30
arkaplan_green = 0.10
arkaplan_blue = 0

Bu yaklaşım iki dezavantaja sahip: gereksiz ayrıntılı (gerçek programlar pencere arka planı, pencere ön planı, menü arka planı vb. için düzinelerce farklı renk tanımlamasına ihtiyaç duyabilir.); ve yaygın renkler için bir öntanımlama yöntemi yok ki akabinde kullanıcı basitçe arkaplan=WHITE gibi bir şey yazabilsin. Bu sakıncaları önlemek için bir rengi temsil etmek için bir tablo kullanacağız:

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

tabloların kullanımı script'e daha fazla malzeme verir; artık kullanıcı için (veya uygulama için) yapılandırma dosyasında daha sonra kullanım için renkleri öntanımlama kolay:

BLUE = {r=0, g=0, b=1}
<diger renk tanimlamalari>

arkaplan = BLUE

C'de bu değerleri elde etmek için devamdaki gibi yapabiliriz:

lua_getglobal(L, "arkaplan");
if (!lua_istable(L, -1))
    error(L, "’arkaplan’ bir tablo değil");

red = getfield(L, "r");
green = getfield(L, "g");
blue = getfield(L, "b");

İlkin, global değişken arkaplan değerini elde edip bunun bir tablo olduğundan emin oluyoruz. Ardından her bir renk bileşenini elde etmek için getfield'i kullanıyoruz. Tabi bu fonksiyon API'nin bir parçası değil; onu tanımlamalıyız. Yine, polimorfizm problemiyle karşı karşıyayız: potansiyel olarak getfield fonksiyonlarının  birçok versiyonu var, anahtar türlü, değer türlü, hata işlemeli vb. değişiyor. Lua API tüm türler için çalışan bir fonksiyon olan lua_gettable 'i sunar. Yığında tablonun konumunu alır, yığından anahtarı pop eder ve karşılık gelen değeri push eder.

Liste 25.2. özel getfield uyarlaması:

#define MAX_COLOR 255

/* tablonun yığının üstünde olduğu varsayılmakta */
int getfield(lua_State *L, const char *anahtar)
{
    int result;
    lua_pushstring(L, anahtar);
    lua_gettable(L, -2); /* arkaplan[anahtar] 'i al*/    if (!lua_isnumber(L, -1))
        error(L, "arkaplan rengi için geçersiz bileşen");
    result = (int)lua_tonumber(L, -1) * MAX_COLOR;
    lua_pop(L, 1); /* sayıyı kaldır */
    return result;
}

25.2 listesinde tanımlanan özel getfield'ımız tablonun yığının üstünde olduğunu varsayar; bu nedenle anahtarı lua_pushstring ile push ettikten sonra tablo indeks -2'de olacaktır. Dönmeden önce, getfield yığından alınan değeri çıkarıp yığını çağrıdan önce olduğu aynı seviyeye getirir.

String anahtarla tablo indeksleme çok yaygın olduğundan Lua 5.1 bu durum için lua_gettable'nin özelleştirilmiş bir versiyonunu sunar: lua_getfield. Bu fonksiyonu kullanarak devamdaki iki satırı 

lua_pushstring(L, anahtar);
lua_gettable(L, -2); /* arkaplan[anahtar] 'i al*/

şöyle yeniden yazabiliriz

lua_getfield(L, -1, anahtar);

(stringi yığına push etmediğimizden lua_getfield'i çağırdığımızda hala tablo indeksi -1'dir).

Örneğimizi biraz daha genişleteceğiz ve kullanıcı için renk adları sunalım. Kullanıcı yine renk tabloları kullanabilir ancak yaygın renkler için önceden tanımlı adlar da kullanabilir. Bu özelliği uygulamak için C uygulamamızda bir renk tablosuna ihtiyacımız var:

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},
    <diger renkler>
    {NULL, 0, 0, 0} /* bitiş simgesi*/
};

Uygulamamız renk adları ile global değişkenler oluşturacak ve renk tabloları kullanılarak bu değişkenleri ilkleyecek. Sonuç, kullanıcının script dosyasında devamdaki satırlara sahip olması ile aynı:

WHITE = {r=1, g=1, b=1}
RED = {r=1, g=0, b=0}
<diger renkler>

Tablo alanlarına değer set etmek için yardımcı bir fonksiyon tanımlıyoruz, setfield; yığına alanın değerini ve indeksini push eder ve  sonra lua_settable'i çağırır:

/* tablonun üstte olduğu varsayalık */
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);
}

Diğer API fonksiyonları gibi lua_settable birçok farklı tip için çalışır bu yüzden yığından tüm işlemsellerini elde eder. Tablo indeksini argüman olarak alıp anahtar ve değer pop eder. setfield fonksiyonu çağrı öncesi tablonun yığının üstünde olduğunu varsayar (indeks -1); indeks ve değer push edildikten sonra tablo indeks -3'de olacaktır.

Lua 5.1 yine string anahtarlar için lua_setfield adında lua_settable'nın özelleştirilmiş bir sürümünü sunar. Bu yeni fonksiyonu kullanılarak  setfield için önceki tanımlamamızı devamdaki gibi yeniden yazabiliriz:

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

Sıradaki fonksiyon setcolor, nihai rengi tanımlar. Bir tablo oluşturur, uygun alanları set eder ve bu tabloyu karşılık gelen global değişkene atar:

void setcolor(lua_State *L, struct ColorTable *ct)
{
    lua_newtable(L); /* bir tablo oluştur */
    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 */
}

lua_newtable fonksiyonu boş bir tablo oluşturur ve yığın üzerine onu koyar; setfield tablo alanlarını set etmek için çağrılır; son olarak, lua_setglobal tabloyu pop eder ve verilen isimli globalin değeri olarak onu set eder.

Bu önceki fonksiyonlarla aşağıdaki döngü, yapılandırma scripti için tüm renkleri kaydeder:

int i = 0;
while (colortable[i].name != NULL)
    setcolor(L, &colortable[i++]);

Uygulamanın script'i çalıştırmadan önce bu döngüyü çalıştırması gerektiğini unutmayın.

Liste 25.3. tablolar veya stringler olarak renkler:

lua_getglobal(L, "arkaplan");
if (lua_isstring(L, -1))
{ /* değer bir string mi? */
    const char *name = lua_tostring(L, -1); /* string'i al */
    int i; /* renk tablosunu ara */
    for (i = 0; colortable[i].name != NULL; i++)
    {
        if (strcmp(colorname, colortable[i].name) == 0)
            break;
    }
    if (colortable[i].name == NULL) /* string değil mi? */
        error(L, "geçersiz renk ismi (%s)", colorname);
    else
    { /* colortable[i] kullan */
        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, "'arkaplan' için geçersiz değer");


25.3 listesinde gösterildiği gibi isimlendirilmiş renkler uygulaması için başka bir seçenek var. global değişkenler yerine kullanıcı renk isimlerini stringler ile gösterebilir, arkaplan="BLUE" şeklinde ayarlarını yazarak. Bu şekilde arkaplan bir tablo veya bir string olabilir. Bu uyarlama ile uygulama kullanıcının script dosyasını çalıştırmadan önce hiçbir şey yapması gerekmez. Bunun yerine bir renk elde etmek için daha fazla işe ihtiyacı var. arkaplan değişkeninin değerini aldığında değer tipinin string olup olmadığını test edip ve sonra renk tablosunda string'e bakması gerekir.

En iyi seçenek ne? C programlarında seçenekleri belirtmek için stringlerin kullanılması iyi bir uygulama değil çünkü derleyici yanlış yazımları algılayamaz. Yine, Lua'da global değişkenlerin deklarasyonuna ihtiyac yoktur mamafi bir kullanıcı bir renk adını yanlış yazdığında Lua herhangi bir hata sinyali vermez. Kullanıcı WRITE yerine WITE yazarsa arkaplan değişkeni nil alır (WITE değeri ilklenmemiş bir değişken) ve uygulamanın bildiği tek şey: bu arkaplan nil. Neyin yanlış olduğu hakkında başka bir bilgi yok. öte yandan stringlerle arkaplan değeri yanlış yazılmış string olacak; bu şekilde uygulama bu bilgileri hata iletisine ekleyebilir. kullanıcı “white”, “WHITE” veya hatta “White” yazabilir, durum ne olursa olsun uygulama aynı zamanda da stringleri karşılaştırabilir. Ayrıca, kullanıcının sadece birkaç seçimi için kullanıcı scriptinin küçük ve birçok renk için yüzlerce rengi kaydetmek (ve yüzlerce tablo ve global değişken oluşturmak)  abes olabilir. stringler ile bu yükü önleyebilirsiniz.

25.3 Lua Fonksiyonlarını Çağırma

Lua'nın büyük gücü olan yapılandırma dosyası, uygulama tarafından çağrılacak fonksiyonlar tanımlayabilir. Örneğin, bir fonksiyonun grafiğini çizecek bir uygulama yazabilir ve çizilecek fonksiyonları tanımlamada Lua'ı kullanabilirsiniz.

Bir fonksiyonu çağırmak için API protokolü basit: ilk olarak çağrılacak fonksiyonu push edersiniz;
ikincisi, çağrı argümanlarını push edersiniz; sonra gerçek çağrıyı yapmak için lua_pcall'ı kullanırsınız; son olarak sonuçları yığından pop edersiniz.

Örnek olarak, yapılandırma dosyanızın devamdaki gibi bir fonksiyona sahip olduğunu varsayalım:

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

Verilen x ve y için z=f(x, y) 'i C'de değerlendirmek istiyorsunuz. Hali hazırda Lua kütüphanesini açtığınızı ve yapılandırma dosyasını çalıştırdığınızı varsayarak bu çağrıyı devamdaki C fonksiyonu içinde kapsülleyebilirsiniz:

/* Lua'da tanımlı 'f' fonksiyonuna çağrı */
double f(double x, double y)
{
    double z;

    /* fonksiyon ve argümanları push et */
    lua_getglobal(L, "f"); /* çağrılacak fonksiyon */
    lua_pushnumber(L, x); /* 1. argümanını push et */
    lua_pushnumber(L, y); /* 2. argümanını push et */

    /* çağrı yap (2 argüman, 1 sonuç) */
    if (lua_pcall(L, 2, 1, 0) != 0)
        error(L, "fonksiyon 'f' yürütülmesinde hata: %s",
              lua_tostring(L, -1));

    /* sonucu al */
    if (!lua_isnumber(L, -1))
        error(L, "fonksiyon 'f' bir sayı döndürmeli");
    z = lua_tonumber(L, -1);
    lua_pop(L, 1); /* döndürülen değeri pop et */
    return z;
}


lua_pcall'ı geçirdiğiniz argüman sayısı ve  talep ettiğiniz sonuç sayısı ile çağırırsınız. Dördüncü argüman bir hata işleme fonksiyonu gösterir; zamanı gelince ele alacağız. Bir Lua atamasında olduğu gibi lua_pcall, gerçek sonuç sayısını talep ettiklerinize göre ayarlar , gerektiğinde ekstra değerler atar veya nil'ler push eder. Sonuçlar push edilmeden önce lua_pcall yığından fonksiyonu ve argümanlarını kaldırır. Bir fonksiyon birden çok sonuç  döndürürse ilk sonuç ilk push edilir; örneğin üç sonuç varsa birincisi indeks -3'te ve sonuncusu indeks -1'de olacak.

lua_pcall çalışırken herhangi bir hata varsa lua_pcall sıfırdan farklı bir değer döndürür; dahası hata iletisini yığına koyar (ama yine de  fonksiyon ve argümanlarını pop eder). Ancak mesaj push edilmeden önce lua_pcall, varsa hata işleyicisi fonksiyonunu çağırır. Bir hata işleyici fonksiyonu belirtmek için lua_pcall'ın son argümanını kullanıyoruz. Sıfır, hata işleyicisi fonksiyonu yok demektir; yani nihai hata mesajı orijinal mesajdır. Aksi halde bu argüman hata işleyici fonksiyonun bulunduğu yığındaki indeksi olmalıdır. Bu durumda işleyici , çağrılacak fonksiyonun ve argümanlarının altındaki konumda stack'da push edilmiş olmalıdır.

Normal hatalar için lua_pcall hata kodu LUA_ERRRUN 'i döndürür. İki tür özel hata farklı kodları hak ediyor çünkü hata işleyicisini asla yürütmezler. İlk tür, bellek ayırma hatasıdır. Bu tür hatalar için lua_pcall daima LUA_ERRMEM döndürür. ikinci tür, Lua'nın hata işleyicinin kendisini yürütürken olan hatadır. Bu durumda tekrar hata işleyicisini çağırmak çok az kullanılır bu nedenle lua_pcall LUA_ERRERR koduyla hemen döner.

25.4 Kapsamlı Çağrı Fonksiyonu

Daha gelişmiş bir örnek olarak, Lua fonksiyonlarını çağırmada kullanacağımız bir paketleme(wrapper) oluşturacağız,  C'deki vararg imkanı kullanılarak. wrapper fonksiyonumuzu call_va olarak isimlendirelim, çağrılacak fonksiyonun adını, argümanların ve sonuçların tiplerini açıklayan bir string, ve devamda argümanların listesi, ve son olarak sonuçların depolanacağı değişkenlere işaretçilerin bir listesini  alır; API'nin tüm detaylarını halleder.  Bu fonksiyonla önceki örneğimizi basitçe şöyle yazabiliriz

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

“dd>d” stringi iki double tipli argüman bir double tipli sonuç anlamına gelir. Bu tanımlayıcı, double için ‘d’  tamsayı için ‘i’ ve string için 's' harfini kullanabilir; '>' argümanları sonuçlardan ayırır. fonksiyonun sonucu yoksa '>' isteğe bağlıdır.

Liste 25.4 call_va fonksiyonu uygulamasını gösterir. Genelliğine rağmen bu fonksiyon ilk örneğimizin aynı adımlarını izler: fonksiyonu push eder, argümanları push eder (Liste 25.5), çağrı yapar ve sonuçları alır (Liste 25.6). Kodunun çoğu basit ancak bazı incelikler var. İlk olarak, func 'ın bir fonksiyon olup olmadığını kontrol etmeye ihtiyaç duymuyor; lua_pcall herhangi bir hata tetikleyecektir. İkincisi, keyfi sayıda argüman push ettiği için yığın alanını kontrol etmelidir.
Üçüncü olarak, fonksiyon stringler döndürebileceğinden call_va sonuçları yığından pop edemez.
string sonuçlar  bittikten sonra(veya onları diğer tamponlara kopyaladıktan sonra), çağrıyı yapanın onları pop etmesi gerekir.

Liste 25.4. kapsamlı çağrı fonksiyonu:

#include <stdarg.h>

void call_va(const char *func, const char *sig, ...)
{
    va_list vl;
    int narg, nres; /* argüman ve sonuçların sayısı */

    va_start(vl, sig);
    lua_getglobal(L, func); /* fonksiyonu push et */

    <argumanlari push et(Liste 25.5)>

    nres = strlen(sig); /* beklenen sonuç sayısı */

    /* çağrı yap */
    if (lua_pcall(L, narg, nres, 0) != 0) /* çağrı yap */
        error(L, "hata çağrısı ’%s’: %s", func,
              lua_tostring(L, -1));

    <sonuclari al(Liste 25.6)>

    va_end(vl);
}

Liste 25.5. kapsamlı çağrı fonksiyonu: argümanların push edilmesi:

for (narg = 0; *sig; narg++){ /* her argüman için tekrarla */

    /* stack alanını kontrol et*/
    luaL_checkstack(L, 1, "çok fazla argüman");

    switch (*sig++)
    {
    case 'd': /* double argüman */
        lua_pushnumber(L, va_arg(vl, double));
        break;
    case 'i': /* int argüman */
        lua_pushinteger(L, va_arg(vl, int));
        break;
    case 's': /* string argüman */
        lua_pushstring(L, va_arg(vl, char *));
        break;
    case '>': /* argümanların sonu */
        goto endargs;
    default:
        error(L, "geçersiz seçim (%c)", *(sig - 1));
    }
}

endargs:

Liste 25.6. kapsamlı çağrı fonksiyonu: sonuçların alınması:

nres = -nres; /* ilk sonucun stack indeksi */
while (*sig){ /* her sonuç için tekrarla */
    switch (*sig++)
    {
    case 'd': /* double sonuç*/
        if (!lua_isnumber(L, nres))
            error(L, "hatalı sonuç tipi");        *va_arg(vl, double *) = lua_tonumber(L, nres);
        break;
    case 'i': /* int sonuç */
        if (!lua_isnumber(L, nres))
            error(L, "hatalı sonuç tipi");        *va_arg(vl, int *) = lua_tointeger(L, nres);
        break;
    case 's': /* string sonuç */
        if (!lua_isstring(L, nres))
            error(L, "hatalı sonuç tipi");
        *va_arg(vl, const char **) = lua_tostring(L, nres);
        break;
    default:
        error(L, "geçersiz seçim (%c)", *(sig - 1));
    }
    nres++;
}

Hiç yorum yok:

Yorum Gönder