24 C API'e Genel Bakış

24 C API'e Genel Bakış

Lua gömme bir dildir. Bu, Lua'nın tek başına bağımsız bir paket olmadığı, Lua imkanlarını dahil etmek üzere diğer uygulamalara linkleyebileceğiniz bir kütüphane olduğu anlamına gelir.

Merak ediyor olabilirsiniz: Lua tek başına bağımsız bir paket değilse nasıl oluyor da tüm kitap boyunca Lua'ı tek başına bağımsız kullandık? Bunun cevabı Lua yorumlayıcısıdır (lua.exe). Bu yorumlayıcı tek başına bağımsız yorumlama uygulaması için Lua kütüphanesini kullanan küçük bir C programıdır (dört yüz satırdan az kod içeren). Bu program kullanıcıyla ara yüz etkileşimini yönetme,  Lua kütüphanesine sunmak üzere kullanıcıdan dosya ve stringlerini alma işini yapar ki bu da işin esaslı unsurudur (gerçekten Lua kodunu yürütme).

Bir uygulamayı genişletmek için kütüphane olarak kullanılma yeteneği Lua'ı eklenti dili yapan şeydir. Aynı zamanda Lua'ı kullanan bir program Lua ortamında yeni fonksiyonlar kaydedebilir ki bu tür fonksiyonlar C (ya da başka bir dille) ile yazılarak doğrudan Lua'da yazılamayan olanaklar ekler. Lua'ı genişletilebilir bir dil yapan şey de budur.

Lua'nın bu iki yönü (eklenti dil ve genişletilebilir bir dil olması), C ve Lua arasındaki iki çeşit etkileşime karşılık gelir. İlki, C kontrole sahiptir Lua kütüphanedir. Bu tür bir etkileşimdeki C kodu uygulama kodu dediğimiz şeydir. İkincisi, Lua kontrole sahiptir C kütüphane. Burada C kodu kütüphane kodu olarak adlandırılır. Hem uygulama kodu hem de kütüphane kodu Lua ile iletişim kurmak için C API olarak adlandırdığımız aynı API'i kullanır.

C API, C kodunun Lua ile etkileşime girmesini sağlayan fonksiyonlar kümesidir. Lua global değişkenlerini okuma ve yazma, Lua fonksiyonlarını çağırma, Lua kod parçalarını yürütme, C fonksiyonlarını kaydetme ve daha sonra Lua kodu ile çağırma vb için fonksiyonlar içerir. (Bu metin boyunca “fonksiyon” terimi aslında “fonksiyon veya makro” anlamında. API,  çeşitli imkanları makro olarak uygular.)

C API, Lua'nın tarzından oldukça farklı olan C'nin tarzını takip eder. C dilinde programlama yaparken tip kontrolü (ve tip hataları), hata kurtarma, bellek tahsisat hataları ve diğer çeşitli karmaşıklık kaynaklarını önemsemeliyiz. API'deki çoğu fonksiyon argümanlarının geçerliliğini kontrol etmez; Bir fonksiyonu çağırmadan önce argümanların geçerli olduğundan emin olmak sizin sorumluluğunuzdadır. Hata yaparsanız iyi niyetli bir hata mesajı yerine "segmentation fault" veya benzeri bir erişim ihlali hatası alabilirsiniz. Dahası, API bazen kullanım kolaylığını feda edip esneklik ve temelliğe önem verir. Sıradan görevler birden fazla API çağrısı içerebilir. Bu sıkıcı olabilir ancak tüm ayrıntılar üzerinde tam kontrol sağlar size.

Başlığın dediği gibi bu bölümün amacı C'den Lua kullanımının neleri kapsadığına genel bir bakış sunmak. Şu ana kadar olanların tüm ayrıntılarını anlamakla uğraşmayın. Daha sonra detaylandıracağız. Bununla birlikte Lua kullanıcı başvuru kılavuzunda ilgili fonksiyonlar hakkında daha fazla bilgi bulabileceğinizi unutmayın. Ayrıca Lua dağıtımının kendisinde API 'nin kullanımının birkaç örneğini bulabilirsiniz. Lua bağımsız yorumlayıcı(lua.c) uygulama kodu, standart kütüphaneler (lmathlib.c, lstrlib.c, vb.) kütüphane kodu örneklerini sunar.

Şu andan itibaren C programcısı şapkasını giyiyoruz. “Siz” dediğimde, C programcısı veya kullandığınız dille C kodlamayı taklit eden sizi kastediyorum.

Lua ve C arasındaki iletişimde önemli bir bileşen her zaman her yerde var olan bir sanal yığın. Neredeyse tüm API çağrıları bu yığındaki değerler üzerinden çalışır. Lua'dan C'e ve C'den Lua'a tüm veri alışverişi bu yığından gerçekleşir. Dahası, ara sonuçları da tutmak için yığını kullanabilirsiniz. Yığın, Lua ve C arasındaki iki keskin uyumsuzluğunun çözülmesine yardımcı olur: birincisi Lua'nın çöp toplamasından kaynaklanır; ikincisi Lua'daki dinamik tipleme ile C'nin statik tipleme arasındaki şoktan kaynaklanır. Yığını Bölüm 24.2'de daha ayrıntılı olarak ele alacağız.

24.1 İlk Örnek

Bu genel bakışa basit bir uygulama programı örneği ile başlayacağız: Bağımsız Lua Yorumlayıcısı. Liste 24.1'deki gibi ilkel bir bağımsız yorumlayıcı yazabiliriz. lua.h başlık dosyası Lua tarafından sağlanan temel fonksiyonları tanımlar. Yeni bir Lua ortamı oluşturma, Lua fonksiyonlarını çağırma (lua_pcall gibi), Lua ortamındaki global değişkenleri okuma ve yazma, Lua tarafından çağrılabilir yeni fonksiyonlar kaydetmek vb için fonksiyonlar içerir. lua.h dosyasında tanımlanan her şey lua_ ön ekine sahiptir.

lauxlib.h başlık dosyası yardımcı kütüphane (auxlib) tarafından sağlanan fonksiyonları tanımlar. Tüm tanımlamaları luaL_ ile başlar(örneğin luaL_loadbuffer). Yardımcı kütüphane daha yüksek bir soyutlama seviyesi sağlamak için lua.h tarafından sağlanan temel API'i kullanır; tüm Lua standart kütüphaneleri auxlib'i kullanır. Temel API ekonomi ve ortogonalite için çaba sarf ederken, auxlib yaygın görevler için pratiklik için çaba göstermektedir. Elbette, programınızın da ihtiyaç duyduğu diğer soyutlamaları oluşturması çok kolay. auxlib, Lua'nın iç kısımlarına erişimi olmadığını unutmayın. Tüm işini resmi temel API üzerinden yapar.


Liste 24.1. Basit bir bağımsız Lua yorumlayıcısı:

#include <stdio.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

int main (void) {
 char buff[256];
 int error;
 lua_State *L = luaL_newstate(); /* Lua'ı aç*/
 luaL_openlibs(L); /* standart kütüphaneleri aç */

 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); /* yığından hata mesajını al */
  }
 }

 lua_close(L);
 return 0;
}

Lua kütüphanesi hiçbir şekilde global değişken tanımlamaz. Tüm durumunu lua_State dinamik yapısında tutar ve bu yapıya bir işaretçi Lua içindeki tüm fonksiyonlara bir argüman olarak geçilir. Bu uygulama Lua'ı umumi ve multithreading kodlamada kullanılmaya hazır hale getirir.

luaL_newstate fonksiyonu yeni bir ortam (veya durum) oluşturur. luaL_newstate yeni bir ortam oluşturduğunda bu ortam öntanımlı fonksiyonlar içermez, hatta print'i bile. Lua'ı küçük tutmak için tüm standart kütüphaneler ayrı paketler olarak sağlanmıştır bu şekilde gerekmediğinde onları kullanmak zorunda kalmazsınız. lualib.h başlık dosyası kütüphaneleri açma fonksiyonlarını tanımlar. luaL_openlibs fonksiyonu tüm standart kütüphaneleri açar.

Bir durum yaratılıp standart kütüphanelerle doldurulduktan sonra kullanıcı girdilerinin yorumlanmasının zamanı gelmiştir. Kullanıcının girdiği her satır için program ilk önce kodu derlemek üzere luaL_loadbuffer fonksiyonunu çağırır. Hata yoksa çağrı sıfır döndürür ve sonuç öbeğini yığına push eder(koyar). (Bu “sihirli” yığını bir sonraki bölümde ayrıntılı olarak ele alacağımızı unutmayın.) Ardından program  yığından öbeği alan(pop eden) lua_pcall 'i çağırır ve korumalı modda onu yürütür. luaL_loadbuffer gibi lua_pcall hata yoksa sıfır döndürür. Hata durumunda her iki fonksiyon da yığına bir hata mesajı koyar; bu mesajı lua_tostring ile alıp yazdırdıktan sonra lua_pop ile yığından kaldırırız.

Hata durumunda bu program hata mesajını standart hata akışına basitçe yazdırır. Gerçek hata işleme C'de oldukça karmaşık olabilir ve bunun nasıl yapılacağı uygulamanızın niteliğine bağlıdır. Lua çekirdeği hiçbir zaman doğrudan herhangi bir çıktı akışına bir şey yazmaz; hata kodlarını ve hata mesajlarını döndürerek hataları bildirir. Her uygulama bu mesajları ihtiyaçlarına en uygun şekilde işleyebilir. Tartışmalarımızı basitleştirmek için bir hata mesajı basıp, Lua durumunu kapatan ve tüm uygulamadan çıkış sağlayan aşağıdaki gibi basit bir hata işleyicimiz olduğunu varsayıyoruz:

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

void error(lua_State *L, const char *fmt, ...){
    va_list argp;
    va_start(argp, fmt);
    vfprintf(stderr, fmt, argp);
    va_end(argp);
    lua_close(L);
    exit(EXIT_FAILURE);
}

Daha sonra, "uygulama kodunda hata işleme" konusunda daha fazlasını ele alacağız.

Lua'ı hem C hem de C++ kodu olarak derleyebildiğiniz için lua.h diğer birkaç C kütüphanesinde mevcut olan devamdaki tipik ayar kodunu içermez:

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

Lua'ı C kodu olarak derlediyseniz (en yaygın durum) ve onu C++ 'de kullanıyorsanız devamdaki gibi lua.h 'ı include etmelisiniz:

extern "C" {
#include "lua.h"
}

24.2 Yığın(Stack)

Lua ve C arasında değer alışverişi yapmaya girişildiğinde iki sorunla karşı karşıyayız: dinamik ve statik tip sistem arasındaki uyuşmazlık ve otomatik ve manuel bellek yönetimi arasındaki uyuşmazlık.

Lua'da, a[k]=v yazdığımızda hem k hem de v farklı tipte olabilir; a bile farklı tipte olabilir, metatablolar nedeniyle. Bununla birlikte, bu işlemi C 'de sunmak istersek herhangi verilen settable fonksiyonu sabit bir tipe sahip olmalı. Bu tek işlem için onlarca farklı fonksiyona ihtiyacımız olacak (üç argüman için herbir tip kombinasyonu için bir fonksiyon).

Bu sorunu C 'de bir union tip türü deklare ederek çözebiliriz, tüm Lua değerlerini temsil edebilecek lua_Value diyelim ona. Ardından, settable'i şöyle deklare edebiliriz:

void lua_settable (lua_Value a, lua_Value k, lua_Value v);

Bu çözümün iki sakıncası var. İlki, böyle kompleks bir tipin diğer dillerde eşleşmesi zor olabilir; Lua yalnızca C/C++ ile değil, Java, Fortran, C# ve benzerleriyle de kolayca arayüz oluşturmak üzere tasarlanmıştır. İkincisi, Lua çöp toplama yapar: bir Lua tablosunu bir C değişkeninde tutarsak Lua motorunun bu kullanım hakkında bilgi sahibi olma yolu yok; bu tablonun çöp olduğunu varsayabilir(yanlış şekilde) ve toplayabilir.

Bu nedenle Lua API lua_Value tipi gibi bir şey tanımlamaz. Bunun yerine Lua ve C arasında değer alışverişi için soyut bir yığın kullanır. Bu yığındaki her yuva herhangi bir Lua değerini tutabilir. Lua'dan bir değer sorgulaması istediğinizde (bir global değişkenin değeri gibi), istenen değeri yığına koyan(push eden) Lua'ı çağırırsınız. Ne zaman Lua'a bir değer iletmek isterseniz, önce değeri yığına koyup sonra Lua'ı (değeri alacak- pop edecek) çağırırsınız. Yığına her bir C tipini push etmek için farklı bir fonksiyona ve yığından her bir değeri elde etmek için yine farklı bir fonksiyona ihtiyacımız var, tabi kombinasyonel patlaktan kaçınıyoruz. Dahası, bu yığın Lua tarafından yönetildiği için çöp toplayıcı hangi değerleri C'nin kullandığını bilir.

API'deki neredeyse tüm fonksiyonlar yığını kullanır. İlk örneğimizde gördüğümüz gibi luaL_loadbuffer sonucunu  (derlenmiş öbek ya da bir hata mesajı) yığına bırakır; lua_pcall yığından çağrılacak fonksiyonu elde eder velev ki olmadı hata mesajı da oraya bırakılır.

Lua, bu yığını sıkı bir LIFO (Son Giren İlk Çıkar) disiplininde manipüle eder. Lua'ı çağırdığınızda yığının sadece en üst kısmını değiştirir. C kodunuz daha fazla özgürlüğe sahip; özellikle, yığının içindeki herhangi bir öğeyi inceleyebilir ve hatta herhangi bir keyfi konuma öğeler ekleyip silebilir.

Öğeleri yığına koyma(push etme)

API, Lua'da temsil edilebilir her C tipi için bir push fonksiyonuna sahiptir: nil sabiti için lua_pushnil, double'lar için lua_pushnumber, tamsayılar için lua_pushinteger, boolean'lar için lua_pushboolean, keyfi stringler(char* artı uzunluk) için lua_pushlstring ve sıfırla sonlandırılmış stringler için lua_pushstring:

void lua_pushnil (lua_State *L);
void lua_pushboolean (lua_State *L, int bool);
void lua_pushnumber (lua_State *L, lua_Number n);
void lua_pushinteger (lua_State *L, lua_Integer n);
void lua_pushlstring (lua_State *L, const char *s, size_t len);
void lua_pushstring (lua_State *L, const char *s);

Aynı zamanda C fonksiyonlarını ve kullanıcı verilerini yığına koyan(push eden) fonksiyonlar da vardır; Onları sonra ele alacağız.

lua_Number tipi Lua'daki sayısal tipdir. Varsayılan olarak double'dır ancak bazı derlemeler Lua'ı sınırlı makinelere entegre etmek için float veya hatta uzun tam sayıya dönüştürebilir bunu. lua_Integer tipi büyük stringlerin boyunu depolamaya yetecek kadar büyük bir işaretli tamsayı tipidir. Genellikle ptrdiff_t tipi olarak tanımlanır.

Lua'daki stringler sıfırla sonlandırılan şekilde değildir; keyfi ikili-binary veri içerebilirler. Sonuç olarak net bir uzunluğa dayanmak zorundadırlar. Bir string'i yığına koymak için kullanılan temel fonksiyon, argüman olarak net uzunluğu isteyen lua_pushlstring fonksiyonudur. Sıfır sonlu stringler için string uzunluğunu elde etmek için strlen'i kullanan lua_pushstring 'i de kullanabilirsiniz. Lua hiçbir zaman dış stringlere (veya daima statik olan C fonksiyonları hariç diğer bir dış nesneye) işaretçiler tutmaz. Tutması gereken herhangi bir string için Lua içsel bir kopya veya yeni bir tanesini oluşturur. Bu nedenle, bu fonksiyonlar geri dönüş yaptığında tamponlarınızı serbest bırakabilir veya modife edebilirsiniz.

Ne zaman bir öğeyi yığına koysanız, yığının onun için yeterli alana sahip olmasını sağlamak sizin sorumluluğunuzdadır. Unutmayın siz şimdi bir C programcısısınız; Lua sizi pışpışlamayacak. Lua başladığı zaman ve Lua'nın C 'i çağırdığı herhangi zamanda, yığının en az 20 boş yuvaya sahiptir (bu sabit lua.h içinde LUA_MINSTACK olarak tanımlanıktır). Bu alan, çoğu yaygın kullanım için fazlasıyla yeterli olduğundan genellikle bunun hakkında kafa yormazsınız. Bununla birlikte bazı görevler daha fazla yığın alanına ihtiyaç duyabilir (örneğin çok fazla argümanlı bir fonksiyon çağırmak). Bu gibi durumlarda, yığının ihtiyaçlarınız için yeterli alana sahip olup olmadığını kontrol eden lua_checkstack'ı çağırmak isteyebilirsiniz:

int lua_checkstack (lua_State *L, int sz);

Öğeleri sorgulama

API, yığındaki öğelere başvuru için indeksleri kullanır. Yığına konan ilk eleman indeks 1'e sahiptir sonraki indeks 2'e ve en üste doğru bu şekilde gider. Aynı zamanda negatif indeksleri kullanarak refaransımız olarak yığının tepesini kullanarak öğelere erişebiliriz. Bu durumda, -1 en üstteki öğeye (yani konan son öğeye), -2 önceki öğeye ve bu şekilde gidecek şekilde başvuru olur. Örneğin, lua_tostring (L, -1) çağrısı bir string olarak yığının en üstündeki değeri döndürür. Göreceğimiz üzere, yığını alttan indekslemenin (yani pozitif indekslerle) doğal karşılanabileceği birkaç durum ve negatif indekslerin kullanmanın doğal olacağı diğer birkaç durum vardır.

Bir öğenin belirli bir tipe sahip olup olmadığını kontrol etmek için API, lua_is* fonksiyonları ailesini sunar; burada *, herhangi bir Lua tipi olabilir. Yani, lua_isnumber, lua_isstring, lua_istable vb. Bütün bu fonksiyonlar aynı prototipe sahiptir:

int lua_is* (lua_State *L, int index);

Aslında lua_isnumber değerin belirli bir tipe sahip olup olmadığını kontrol etmez ama değerin bu tipe dönüştürülebilir olup olmadığını kontrol eder; lua_isstring 'de benzer şekilde. Örneğin herhangi sayı lua_isstring 'i tatmin eder.

Ayrıca yığındaki bir öğenin tipini döndüren lua_type fonksiyonu da vardır. Her tip lua.h başlık dosyasında tanımlı bir sabitle temsil edilir: LUA_TNIL, LUA_TBOOLEAN, LUA_TNUMBER, LUA_TSTRING, LUA_TTABLE, LUA_TTHREAD, LUA_TUSERDATA ve LUA_TFUNCTION. Bu fonksiyon temel olarak bir switch deyimiyle birlikte kullanılır. Zorlamadan, stringleri ve sayıları kontrol etmemiz gerektiğinde de kullanışlıdır.

Yığından bir değer elde etmek için lua_to* fonksiyonları vardır:

int lua_toboolean (lua_State *L, int index);
lua_Number lua_tonumber (lua_State *L, int index);
lua_Integer lua_tointeger (lua_State *L, int index);
const char *lua_tolstring (lua_State *L, int index, size_t *len);
size_t lua_objlen (lua_State *L, int index);

Verilen öğe geçerli tipe sahip olmasa bile çağrılmaları sorun olmaz. Bu durumda, lua_toboolean, lua_tonumber, lua_tointeger ve lua_objlen sıfır döndürür; diğer fonksiyonlar NULL döndürür. Sıfır kullanışlı değil ancak ANSI C bize hataları bildirmek için kullanabileceğimiz geçersiz olmayan bir sayısal değer sunar. Diğer fonksiyonlar için mamafi  sık sık ilgili lua_is * fonksiyonunu kullanmamız gerekmez: sadece lua_to* fonksiyonunu çağırır ve sonra sonucun NULL olup olmadığını test ederiz.

lua_tolstring fonksiyonu string'in içsel bir kopyasına bir işaretçi döndürür ve string'in uzunluğunu len ile verilen konumda saklar. Bu içsel kopyayı değiştiremezsiniz (hatırlayın orada bir const var). Lua, bu işaretçinin  yığında karşılık gelen string değeri olduğu sürece geçerli olmasını sağlar. Lua tarafından çağrılan bir C fonksiyonu dönüş yaptığında Lua onun yığınını temizler; Bu nedenle bir kural olarak,  onları alan fonksiyon dışında Lua stringlerine işaretçileri hiçbir zaman depolamamalısınız.

lua_tolstring'in döndürdüğü herhangi bir string daima sonunda ekstra bir sıfıra sahiptir, tabi içinde başka sıfırlara da sahip olabilir. Üçüncü argümanla döndürülen büyüklük,  len, string'in gerçek uzunluğudur. Özellikle, yığının en üstündeki değerin bir string olduğu varsayılarak, aşağıdaki savlar daima geçerlidir:

size_t l;
const char *s = lua_tolstring(L, -1, &l); /* herhangi Lua string'i */
assert(s[l] == '\0');
assert(strlen(s) <= l);

Uzunluğa ihtiyacınız yoksa lua_tolstring'i üçüncü argümanı olarak NULL ile çağırabilirsiniz. Daha da iyisi üçüncü argümanı NULL ile lua_tolstring'i basitçe çağıran lua_tostring makrosunu kullanabilirsiniz.

lua_objlen fonksiyonu bir nesnenin “uzunluğunu” döndürür. stringler ve tablolar için bu değer '#' uzunluk operatörüyle aynı sonucu verir. Bu fonksiyon aynı zamanda kullanıcı verisinin tam büyüklüğünü elde etmek için de kullanılabilir. (Bölüm 28.1'deki kullanıcı verilerini ele alacağız.)

Bu fonksiyonların kullanımını göstermek için Liste 24.2 yığının tüm içeriğini döken(dump) yararlı bir yardımcı fonksiyon sunar. Bu fonksiyon yığını aşağıdan yukarıya geçip , tipine göre her bir öğeyi  yazdırıyor. tırnaklar arasında stringleri yazdırır; sayılar için '%g' formatını kullanır; diğer değerler için (tablolar, fonksiyonlar, vb.) sadece tiplerini yazdırır (lua_typename, tip kodunu tip ismine çevirir).

Diğer yığın işlemleri

C ve yığın arasında değer alışverişi sağlayan önceki fonksiyonların dışında aynı zamanda API, genel amaçlı yığın manipülasyonu için aşağıdaki işlemleri de sunar:

int lua_gettop (lua_State *L);
void lua_settop (lua_State *L, int index);
void lua_pushvalue (lua_State *L, int index);
void lua_remove (lua_State *L, int index);
void lua_insert (lua_State *L, int index);
void lua_replace (lua_State *L, int index);

lua_gettop fonksiyonu aynı zamanda en üstteki öğenin indeksi olan yığındaki öğelerin sayısını döndürür. lua_settop en üstü (yani, yığındaki öğe sayısı) belli bir değer set eder. Önceki en üst yenisinden daha yüksekse yüksekteki değerler atılır. Tersine , daha düşükse fonksiyon verilen boyutu elde etmek için nil leri yığın üstüne koyar. Özel bir durum olarak lua_settop(L, 0) yığını boşaltır. lua_settop ile negatif indeksler de kullanabilirsiniz. Bu imkan kullanılarak API, yığından n adet öğeyi çıkaran(pop eden) devamdaki makroyu sunar:

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

lua_pushvalue fonksiyonu yığına verilen indeksteki öğenin bir kopyasını koyar; lua_remove verilen indeksteki elemanı kaldırır, boşluğu doldurmak için bu pozisyonun üstündeki tüm öğeleri aşağı kaydırır; lua_insert en üst öğeyi verilen pozisyona taşır, alan açmak için bu pozisyonun üstündeki tüm öğeleri yukarı kaydırır; Son olarak, lua_replace en üstten değer alır(pop) ve hiçbir şeyi hareket ettirmeden verilen indeks değeri olarak onu set eder. Aşağıdaki işlemlerin yığın üzerinde hiçbir bir etkisi olmadığına dikkat:

lua_settop(L, -1); /* en üstün geçerli değerini ayarla */
lua_insert(L, -1); /* üst öğeyi enüste taşı */

Liste 24.2. yığın dökümü:

static void stackDump(lua_State *L){
    int i;
    int top = lua_gettop(L);
    for (i = 1; i <= top; i++) { /* her seviye için tekrarla */
        int t = lua_type(L, i);
        switch (t) {
            case LUA_TSTRING: { /* stringler */
                printf("’%s’", lua_tostring(L, i));
                break;
            }
            case LUA_TBOOLEAN: { /* booleanlar */
                printf(lua_toboolean(L, i) ? "true" : "false");
                break;
            }
            case LUA_TNUMBER: { /* sayılar */
                printf("%g", lua_tonumber(L, i));
                break;
            }
            default: { /* diğer değerler */
                printf("%s", lua_typename(L, t));
                break;
            }
        }
        printf(" "); /* ayırıcı koy */
    }
    printf("\n"); /* listeleme sonu */
}

Liste 24.3'teki program bu yığın işlemlerini göstermek için stackDump'ı (Liste 24.2'de tanımlanmıştır ) kullanır.

Liste 24.3. yığın manipülasyon örneği:

#include <stdio.h>
#include "lua.h"
#include "lauxlib.h"
static void stackDump(lua_State *L){
    <Liste 24.2 deki gibi>
}
int main(void){
    lua_State *L = luaL_newstate();

    lua_pushboolean(L, 1);
    lua_pushnumber(L, 10);
    lua_pushnil(L);
    lua_pushstring(L, "hello");

    stackDump(L);
                        /* true 10 nil ’hello’ */
    lua_pushvalue(L, -4);    stackDump(L);
                        /* true 10 nil ’hello’ true */
    lua_replace(L, 3);    stackDump(L);
                        /* true 10 true ’hello’ */
    lua_settop(L, 6);    stackDump(L);
                        /* true 10 true ’hello’ nil nil */
    lua_remove(L, -3);    stackDump(L);
                        /* true 10 true nil nil */
    lua_settop(L, -5);    stackDump(L);
                        /* true */
    lua_close(L);
    return 0;
}


24.3 C API ile Hata işleme

C++ veya Java'nın aksine, C dili bir istisna işleme mekanizması sunmaz. Bunun üstünden gelmek için Lua, istisnaların ele alınmasına benzer bir mekanizma üreten C'den setjmp imkanını kullanır.

Lua'daki tüm yapılar dinamiktir: ihtiyaç duyulduğunda büyürler ve mümkün olduğunda tekrar küçülürler. Bu, bellek tahsisat başarısızlığı ihtimalinin Lua'da muhtemel olduğu anlamına gelir. Neredeyse her işlem bu olasılığa maruz kalabilir. API'sinde her işlem için hata kodları kullanmak yerine Lua, bu hataları bildirmek için istisnaları kullanır. Bu, hemen hemen tüm API fonksiyonlarının geri dönüş yerine hata fırlatabileceği(yani longjmp çağrısı) anlamına gelir.

Kütüphane kodu yazdığımızda (yani Lua'dan çağrılacak C fonksiyonları) uzun atlamaların(long jumps) kullanımı neredeyse gerçek istisna işleme tesisi kadar yararlıdır çünkü Lua imkan bulan her hatayı yakalar. Bununla birlikte uygulama kodunu yazdığımızda (yani Lua'ı çağıran C kodu) bu hataları yakalamanın bir yolunu sağlamalıyız.

Uygulama kodunda hata işleme

Genellikle uygulama kodunuz korumasız yürütülür. Kodu Lua tarafından çağrılmadığından Lua hataları yakalamak için uygun bağlamı kuramaz(yani setjmp'ı çağıramaz). Bu tür ortamlarda Lua “bellek yetersiz” gibi bir hataya yöneldiğinde yapılabilecek fazla bir şey yok. Panik fonksiyonunu çağırır ve eğer bu fonksiyon dönerse uygulama sonlanır. Kendi panik fonksiyonunuzu lua_atpanic fonksiyonu ile set edebilirsiniz.

Bütün API fonksiyonları istisnalar fırlatmaz. luaL_newstate, lua_load, lua_pcall ve lua_close fonksiyonlarının tümü güvenlidir. Ayrıca, diğer birçok fonksiyon yalnızca bellek ayırma hatası durumunda bir istisna fırlatabilir: örneğin, dosya adının bir kopyası için yeterli bellek yoksa luaL_loadfile başarısız olur. Bazı programların bellek tükendiğinde yapacak bir şeyleri olmaz bu nedenle bu istisnaları görmezden gelebilirler. Bu programlar için, Lua’nın belleği tükenirse paniklemesi gerek.

Uygulamanızın kapatılmasını istemiyorsanız, bir bellek tahsis hatası durumunda bile, iki seçeneğiniz var. Birincisi, Lua'a dönmeyen bir panik fonksiyonu set etmek, örneğin kendi setjmp'ınıza bir longjmp kullanımı. İkincisi, kodunuzu korumalı modda çalıştırmaktır.

Çoğu uygulama (bağımsız yorumlayıcı dahil), lua_pcall çağrısıyla Lua kodunu çalıştırır; bu şekilde tipik olarak Lua kodunuz korumalı modda çalışacaktır. Bellek ayırma hataları durumunda bile lua_pcall, yorumlayıcıyı tutarlı bir durumda ayakta tutarak hata kodu döndürür. Ayrıca, Lua ile etkileşimde olan tüm C kodlarınızı korumak istiyorsanız aynı şekilde lua_cpcall'u kullanabilirsiniz. Bu fonksiyon lua_pcall fonksiyonuna benzer ama çağrılacak C fonksiyonunu argüman olarak alır, bu nedenle verilen fonksiyon yığına konurken bellek ayırma hatası tehlikesi yoktur.

Kütüphane kodunda hata işleme

Lua güvenli bir dildir. Bu, ne yazarsanız yazın ne kadar yanlış olursa olsun, Lua'nın kendi terminolojisinde bir programın davranışını daima anlayabileceğiniz anlamına gelir. Ayrıca, hatalar tespit edilip Lua termilojisinde açıklanmıştır da. Bu, birçok yanlış program davranışlarının sadece ilkel temel donanım bazlı açıklandığı ve hata konumlarının bir program sayacı gibi verildiği C ile farklı durumdur.

Lua'a yeni her C fonksiyonu eklediğinizde güvenliğini kırabilirsiniz. Örneğin keyfi bir bellek adresindeki keyfi bir baytı depolayan C'deki poke gibi bir fonksiyon her türlü bellek düzeni bozulmasına neden olabilir. Eklentilerinizin Lua'a güvenli olmasına ve iyi hata yönetimi sağlamaya çaba göstermelisiniz.

Daha önce ele aldığımız üzere her C programının hataları işlemek için kendi yöntemi vardır. Ancak, Lua için kütüphane fonksiyonları yazarken hataları işlemenin standart bir yöntemi vardır. Bir C fonksiyonu bir hata tespit ettiğinde basitçe lua_error 'ı çağırır (veya daha iyisi luaL_error'ı çağırır ki bu hata mesajını biçimleyip ardından lua_error'ı çağırır). lua_error fonksiyonu Lua'da temizlenmesi gerekenleri temizler ve hata mesajını ileterek bu yürütmeyi başlatan lua_pcall'a geri dönüş sağlar.

Hiç yorum yok:

Yorum Gönder