27 C fonksiyonları yazma teknikleri

27 C fonksiyonları yazma teknikleri

Hem resmi API hem de yardımcı kütüphane, C fonksiyonlarının yazılmasına destek olmak için çeşitli mekanizmalar sağlar. Bu bölümde dizi manipülasyonu, string manipülasyonu ve C'de Lua değerlerini depolamak için mekanizmaları ele alıyoruz.

27.1 Dizi Manipülasyonu

Lua'da dizi(array), özgül bir şekilde kullanılan tablo için sadece bir addır. Dizileri, tabloları manipüle etmek için kullandığımız lua_settable  ve lua_gettable  isimli aynı fonksiyonlarla manipüle edebiliriz. Bununla birlikte API, dizi manipülasyonu için özel fonksiyonlar sağlar. Bu ekstra fonksiyonların bir nedeni performanstır: sık sık, bir algoritmanın(örneğin sıralama) iç döngüsü  içinde dizi-erişim işlemleri yaparız ki bu işlemdeki herhangi bir performans kazanımı algoritmanın genel performansı üzerinde büyük bir etkiye sahip olabilir.  Diğer bir sebep kolaylık: string anahtarlar gibi tamsayı anahtarları bazı özel muameleleri hak edecek kadar yaygındır.

API, dizi manipülasyonu için iki fonksiyon sağlar:

void lua_rawgeti (lua_State *L, int index, int key);
void lua_rawseti (lua_State *L, int index, int key);

yukarıdaki lua_rawgeti ve lua_rawseti tanımlamasındaki iki indeks(index ve key) biraz kafa karıştırıcı gelebilir: index, tablonun yığında olduğu yere işaret eder; key, öğenin tabloda olduğu yere işaret eder.

Liste 27.1. C'de map fonksiyonu:

int l_map(lua_State *L)
{
  int i, n;

  /* 1. argüman bir tablo (t) olmalı*/
  luaL_checktype(L, 1, LUA_TTABLE);

  /* 2. argüman bir fonksiyon (f) olmalı */
  luaL_checktype(L, 2, LUA_TFUNCTION);

  n = lua_objlen(L, 1); /* tablo boyunu getir*/

  for (i = 1; i <= n; i++)
  {
    lua_pushvalue(L, 2); /* f'i push et*/
    lua_rawgeti(L, 1, i); /* t[i] 'i push et*/
    lua_call(L, 1, 1); /* f(t[i]) 'i çağır*/
    lua_rawseti(L, 1, i); /* t[i] = sonuç */
  }

  return 0; /* sonuç yok */
}

lua_rawgeti(L,t,key) çağrısı t pozitif olduğunda (aksi takdirde yığında yeni öğeyi tazmin etmelisiniz) devamdaki sekansla eşdeğerdir:

lua_pushnumber(L, key);
lua_rawget(L, t);

lua_rawseti(L,t,key) çağrısı (yine t pozitif için) şu sekansa eşdeğerdir:

lua_pushnumber(L, key);
lua_insert(L, -2); /* ’key’ 'i önceki değerin altına koy */
lua_rawset(L, t);

Her iki fonksiyon da ham(raw) işlemler kullanır unutmayın. Bunlar daha hızlıdır ve, zaten, diziler olarak kullanılan tablolar nadiren metametotlar kullanır.

Bu fonksiyonların kullanımının somut bir örneği olarak Liste 27.1, map fonksiyonu uygulamasıdır: bir dizinin tüm öğelerine , her bir öğenin çağrının sonucuna göre yerinin değiştirildiği, verilen bir fonksiyonu uygular. Bu örnek aynı zamanda iki yeni fonksiyon sunar. luaL_checktype fonksiyonu (lauxlib.h'den) verilen bir argümanın verilen bir tipe sahip olmasını sağlar; aksi halde bir hata yükseltir. lua_call fonksiyonu korumasız çağrı yapar. lua_pcall'a benzer ancak hata durumunda bir hata kodu döndürmek yerine hata üretir. Bir uygulamada ana kodu yazarken herhangi bir hata yakalamak istediğiniz de lua_call 'i kullanmamalısınız. Bununla birlikte fonksiyonlar yazarken lua_call kullanmak genelde iyi bir fikirdir; bir hata varsa bunu önemseyene sadece bırakır.

Liste 27.2. bir stringi bölme:

static int l_split(lua_State *L)
{
  const char *s = luaL_checkstring(L, 1);
  const char *sep = luaL_checkstring(L, 2);
  const char *e;
  int i = 1;

  lua_newtable(L); /* sonuç */

  /* herbir ayırıcı için tekrarla */
  while ((e = strchr(s, *sep)) != NULL)
  {
    lua_pushlstring(L, s, e - s); /* alt stringi push et */
    lua_rawseti(L, -2, i++);
    s = e + 1; /* ayırıcıyı atla */
  }

  /* son alt stringi push et */
  lua_pushstring(L, s);
  lua_rawseti(L, -2, i);

  return 1; /* tablo döndür */
}

27.2 String Manipülasyonu

Bir C fonksiyonu Lua'dan bir string argüman aldığında gözlemlemesi gereken tam iki kural vardır: ona erişirken stringi yığından çıkarmamak ve stringi asla değiştirmemek.

Bir C fonksiyonu Lua'a döndürülecek bir string oluşturmaya ihtiyaç duyduğunda işler daha zorlu olur.
Artık, tampon-arabellek(buffer) tahsis/tasfiye, tampon taşması ve benzerleri ile ilgilenmek için C kodu tertiplenmelidir. Tabi Lua API bu bu görevlere yardımcı olmak için bazı fonksiyonlar sağlar.

Standart API en temel string işlemlerinden ikisi için destek sağlar: alt string çıkarma ve string birleştirme. Bir alt stringi çıkarmak için temel işlem lua_pushstring string uzunluğunu ekstra bir argüman olarak alır unutmayın. Bu nedenle Lua'a bir s stringinin i konumundan j'e (dahil) kadar  bir alt(bölümü) stringini geçmek isterseniz yapmanız gereken tek şey şudur:

lua_pushlstring(L, s + i, j - i + 1);

Örnek olarak, belirli bir ayırıcıya (tek bir karakter) göre bir stringi bölen ve alt stringlerle bir tablo döndüren bir fonksiyon istediğinizi varsayalım. Örneğin, split("hi,ho,there",",") çağrısı,{"hi","ho","there"} tablosunu döndürmelidir. Liste 27.2 bu fonksiyon için basit bir uygulama sunar.
Ekstra tamponlara ihtiyaç duymaz ve işleyebileceği stringlerin boyutu için herhangi bir kısıtlama koymaz.

stringleri bitiştirmek için, Lua API belirli bir fonksiyon sağlar, lua_concat isimli. Lua'daki .. operatörüne benzer: bu, stringleri sayılara dönüştürür ve gerektiğinde metametotlar tetikler. Dahası, aynı anda iki stringden fazlasını birleştirebilir. lua_concat(L,n)  çağrısı, yığının üstündeki n değerlerini birleştirir (ve pop) ,sonucu üste koyar(push eder).

Başka bir yararlı fonksiyon lua_pushfstring:

const char *lua_pushfstring (lua_State *L, const char *fmt, ...);

sprintf C fonksiyonuna biraz benzer , bunda , bir biçimleme stringi ve bazı ekstra argümanlara göre bir string oluşturulur. sprintf aksine, ancak, bir tampon sağlamanıza gerek yoktur. Lua dinamik olarak sizin için string oluşturur, olması gerektiği kadar büyük. Tampon taşması ve benzerleri hakkında endişeye gerek yok. fonksiyon, elde edilen string yığına koyar(push) ve bir işaretçi döndürür. Hazırda, bu fonksiyon yalnızca % % (‘%’karakteri için), %s (stringler için), %d (tamsayılar için), %f (Lua sayıları için, yani double) ve %c (bir tamsayı alır ve bir karakter olarak biçimlendirir) direktiflerini kabul eder . genişlik veya hassasiyet gibi bir seçeneği kabul etmez.

sadece birkaç string birleştirmek istediğimizde hem lua_concat hem de lua_pushfstring yararlıdır.
Ancak, birçok stringi biraraya birleştirmemiz gerekiyorsa (veya karakter), 11.6 bölümünde gördüğümüz gibi tek-tek yaklaşımı oldukça verimsiz olabilir. Bunun yerine, yardımcı kütüphane tarafından sağlanan tampon imkanlarını kullanabiliriz. auxiliary(yardımcı) kütüphanesi bu tamponları iki seviyede uygular. İlk seviye G/Ç işlemlerindeki tamponlara benzer: yerel bir tampona küçük stringleri(veya tek tek karakterler) toplar ve tampon dolduğunda onları Lua'a (lua_pushlstring ile) geçer. İkinci seviye, lua_concat ve 11.6 bölümünde gördüğümüz çoklu tampon birleşimlerinin boşaltılması(flush) olan stack algoritmasının bir varyantını kullanır.

daha ayrıntılı auxlib'den tampon imkanlarını betimlemek için basit bir kullanım örneğine bakalım.
Bir sonraki kod, string.upper fonksiyonunun uygulanmasını gösterir, lstrlib.c kaynak dosyasından:

static int str_upper(lua_State *L)
{
  size_t l;
  size_t i;
  luaL_Buffer b;
  const char *s = luaL_checklstr(L, 1, &l);
  luaL_buffinit(L, &b);
  for (i = 0; i < l; i++)
    luaL_addchar(&b, toupper((unsigned char)(s[i])));
  luaL_pushresult(&b);
  return 1;
}

ilk adım auxlib 'den bir tampon kullanmak luaL_Buffer tipiyle bir değişken deklare edilmesi ve sonra luaL_buffinit çağrısıyla onun ilklenmesidir. ilkleme işleminden sonra, tampon, durum L'nin bir kopyasını tutar, bu şekilde tamponu manipüle eden diğer fonksiyonları çağırırken onu geçirmemize
gerek yoktur. luaL_addchar makrosu tampona tek bir karakter koyar. Auxlib ayrıca açık bir uzunluk (luaL_addlstring) ve sıfırla sonlandırılmış stringleri  (luaL_addstring) ile stringleri tampona koymak için fonksiyonlar sunar. Son olarak, luaL_pushresult tamponu boşaltır(flush) ve nihai stringi yığının üstüne bırakır. Bu fonksiyonların prototipleri aşağıdaki gibidir:

void luaL_buffinit(lua_State *L, luaL_Buffer *B);
void luaL_addchar(luaL_Buffer *B, char c);
void luaL_addlstring(luaL_Buffer *B, const char *s, size_t l);
void luaL_addstring(luaL_Buffer *B, const char *s);
void luaL_pushresult(luaL_Buffer *B);

Bu fonksiyonları kullanarak, tampon tahsisi, taşmalar ve diğer benzerayrıntılar hakkında endişelenmenize gerek yoktur. Dahası, gördüğümüz gibi, birleştirme algoritması oldukça etkilidir. str_upper fonksiyonu herhangi bir sorun olmadan büyük stringleri ( Mbayt'dan fazla) işler.

auxlib tamponu kullandığınızda, bir ayrıntı hakkında endişelenmeniz gerekir. Tampona bir şeyler koyduğunuzda, Lua yığınında bazı ara sonuçlar tutar. Bu nedenle, tampon kullanmaya başlamadan önce yığın üstünün nerede kalacağını hükmedemezsiniz. Ayrıca, bir tampon kullanılırken (başka bir tampon oluşturulmasında bile) diğer görevler için yığını kullanabilirsiniz, ancak bu kullanımlar için push/pop sayısı tampona her eriştiğinizde dengelenmelidir. Bu kısıtlamanın çok şiddetli olduğu,  Lua'dan döndürülen bir stringi tampona koymak istediğinizde belirginleşen bir durum vardır. Bu gibi durumlarda, tampona onu eklemeden önce stringi pop edemezsin çünkü stack'dan o pop edildikten sonra Lua'dan bir string aslan kullanamazsın  ama aynı zamanda pop etmeden önce tampona stringi ekleyemezsin.

Bu sık bir durum olduğundan, auxlib, yığının üst kısmındaki değeri tampona eklemek için özel bir fonksiyon sağlar:

void luaL_addvalue (luaL_Buffer *B);

Elbette, üstteki değer bir string veya sayı değilse, bu fonksiyonu çağırmak bir hatadır.

27.3 C fonksiyonlarında Durumu Depolama

Sıklıkla, C fonksiyonlarının yerel olmayan bazı verileri, yani daha uzun ömürlü verileri tutması gerekir. C'de, genellikle bu ihtiyaç için global veya statik değişkenler kullanıyoruz. Ancak, Lua için kütüphane fonksityonları programlarken, global ve statik değişkenler iyi bir yaklaşım değildir. İlk olarak, genel bir Lua değerini bir C değişkeninde saklayamazsınız. ikincisi, bu tür değişkenleri kullanan bir kütüphane, çoklu Lua durumların'da kullanılamaz.

Bir Lua fonksiyonu, yerel olmayan verileri depolamak için üç temel yere sahiptir: global değişkenler, fonksiyon ortamları ve yerel olmayan değişkenler. C API aynı zamanda yerel olmayan veriyi depolayacak üç temel yer sunar: registry, ortamlar ve üstdeğerler(upvalues).

registry, sadece C kodu tarafından erişilebilen global bir tablodur. Genellikle, birkaç modül arasında paylaşılacak verileri depolamak için kullanabilirsiniz. Verileri özel olarak bir modülde saklamanız gerekiyorsa, ortamları kullanmalısınız. Bir Lua fonksiyonu gibi, her C fonksiyonunun kendi ortam tablosu vardır. Genellikle bir modüldeki tüm fonksiyonlar aynı ortam tablosunu paylaşır, böylece verileri paylaşabilirler. Son olarak, bir C fonksiyonu da aynı zamanda bu özel fonksiyonla ilişkilendirilmiş Lua değerleri olan üstdeğerlere(upvalues) sahip olabilir.

Registry

Registry, her zaman değeri LUA_REGISTRY ile tanımlı değer olan bir sözde-indeks'de(pseudo-index) konumlanır. sözde-indeks, stack'daki bir indeks gibidir, ilişkilendirilmiş değeri stack'da değildir. argüman olarak indekslerin kabul eden çoğu lua api'deki fonksiyon sözde-indeksleri de kabul eder-istisnalar, lua_remove ve lua_insert gibi yığını kendisi manipüle eden fonksiyonlardır. Örneğin, registry'de  “Key” anahtarı ile saklı bir değeri almak için aşağıdaki çağrıyı kullanabilirsiniz :

lua_getfield(L, LUA_REGISTRYINDEX, "Key");

registry normal bir Lua tablosudur. Bu nedenle, herhangi bir Lua değeriyle onu indeksleyebilirsiniz ancak nil. Bununla birlikte, tüm C modülleri aynı registry'i paylaştığından, çarpışmaları önlemek için anahtarlar olarak hangi değerleri kullandığınıza dikkatle seçmelisiniz. String anahtarlar, diğer bağımsız kütüphanelerin verilerinize erişmesine izin vermek istediğinizde özellikle yararlıdır, çünkü bilmeleri gereken tek şey anahtar adıdır. Bu tür anahtarlar için, isim seçmenin kurşun geçirmez bir yöntemi yoktur, ancak yaygın isimlerden kaçınmak ve isimlerinizi kütüphane adıyla veya benzer bir şeyle öneklemek gibi bazı iyi uygulamalar vardır. lua veya lualib gibi önekler iyi seçimler değildir. Başka bir seçenek, çoğu sistemin artık bu tür tanımlayıcıları  oluşturmak için programları olduğu için(örneğin, Linux'ta uuidgen) üniversal bir benzersiz tanımlayıcı (uuid) kullanmaktır. Bir uuid, ana bilgisayar MAC adresi , zaman ve rastgele bir bileşenin bir kombinasyonuyla üretilen 128-bitlik bir sayıdır(alfasayısal bir string oluşturmak için onaltılık-hex olarak yazılır) , böylece diğer herhangi uuıd'den kesinlikle farklıdır.

registry'de anahtarlar olarak sayıları asla kullanmamalısınız, çünkü bu tür anahtarlar referans sistemi için ayrılmıştır. Bu sistem, benzersiz adların nasıl oluşturulacağı konusunda endişelenmeden değerleri bir tabloda saklamanıza izin veren yardımcı kütüphanedeki birkaç fonksiyon ile oluşur. devamdaki çağrı:

int r = luaL_ref(L, LUA_REGISTRYINDEX);

yığından bir değer alır, taze bir tamsayı anahtarla registy'de onu depolar. Bu refarans anahtarı çağırırız.

Adından da anlaşılacağı üzere, bir C yapısı içinde bir Lua değerine bir başvuru saklamak ihtiyaç duyduğumuzda çoğunlukla referansları kullanırız. Gördüğümüz üzere, işaretçileri onları alan C fonksiyonları dışında Lua stringlerinde asla saklamamalıyız. Dahası, Lua, tablolar veya fonksiyonlar gibi diğer nesnelere işaretçiler bile sunmaz. Bu nedenle, işaretçiler aracılığıyla Lua nesnelerine başvuramayız. Bunun yerine, bu tür işaretçilere ihtiyacımız olduğunda, bir referans oluşturuyoruz ve C'de saklıyoruz.

bir r referans ile ilişkilendirilmiş değeri yığın üzerine push etmek içmek sadece şunu yazarız:

lua_rawgeti(L, LUA_REGISTRYINDEX, r);

Son olarak, hem değeri hem de referansı serbest bırakmak için:

luaL_unref(L, LUA_REGISTRYINDEX, r);

Bu çağrıdan sonra, luaL_ref için yeni bir çağrı ile tekrar bu referans döndürülebilir.

Referans sistemi nil'i özel bir durum olarak görür. bir nil değeri için luaL_ref 'i çağırdığımızda, yeni bir referans oluşturmaz, ancak bunun yerine sabit referans LUA_REFNIL'i döndürür. şu çağrının:

luaL_unref(L, LUA_REGISTRYINDEX, LUA_REFNIL);

hiçbir etkisi yoktur, oysa:

lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_REFNIL);

beklendiği gibi bir nil push eder.

Başvuru sistemi aynı zamanda sabit LUA_NOREF'i tanımlar, ki herhangi geçerli referansdan farklı bir tamsayıdır. Başvuruları geçersiz olarak işaretlemek için yararlıdır. LUA_REFNIL ile ki gibi, LUA_NOREF'ı almak için herhangi bir girişim nil döndürür ve serbest bırakmada herhangi bir girişimin hiçbir etkisi yoktur.

registry 'de anahtarlar oluşturmak için bir başka kurşun geçirmez yöntem, kodunuzdaki statik bir değişkenin adresini anahtar olarak kullanmaktır: C link editörü, bu anahtarın tüm kütüphaneler arasında benzersiz olmasını sağlar. Bu seçeneği kullanmak için gereken fonksiyon lua_pushlightuserdata, Lua yığını üzerine bir C işaretçisini temsil eden bir değer push eder.
Aşağıdaki kod, bu yöntemi kullanarak registry'den bir string  alma ve depolamanın nasıl olduğunu gösterir:

/* benzersiz adresli değişken */
static const char Key = 'k';

/* stringi depola */
lua_pushlightuserdata(L, (void *)&Key); /* adresi push et */
lua_pushstring(L, myStr); /* değeri push et */
lua_settable(L, LUA_REGISTRYINDEX); /* registry[&Key] = myStr */

/* stringi al */
lua_pushlightuserdata(L, (void *)&Key); /* adresi push et */
lua_gettable(L, LUA_REGISTRYINDEX); /* değeri al */
myStr = lua_tostring(L, -1); /* string'e çevir */

Bölüm 28.5'de daha ayrıntılı olarak hafif kullanıcı verisini(light userdata) ele alacağız.

C fonksiyonları için ortamlar

Lua 5.1 'den beri, Lua kaydettiğimiz herbir C fonksiyonun kendi ortam tablosu vardır. Bir fonksiyon, sözde-indeks ile registry eriştiği şekilde ortamına erişebilir. Ortam için özde-indeks LUA_ENVİRONINDEX'dir.

Tipik olarak, bu ortamları Lua modülleri için ortamları kullandığımız şekilde kullanıyoruz. Modül için yeni bir tablo oluşturur ve tüm fonksiyonlarını bu tabloyu paylaştırırız. Bu tür paylaşılan ortamları C'de set etmenin yolu, bu ortamları Lua'da set etmemize benzer: ana chunk'ın ortamı basitçe değiştiririz ki oluşturduğu tüm fonksiyonlar yeni ortamı otomatik olarak miras alır. C'de, böyle bir ortamı set etme kodu devamdaki gibi görünür :

int luaopen_foo(lua_State *L){
  lua_newtable(L);
  lua_replace(L, LUA_ENVIRONINDEX);
  luaL_register(L, <libname>, <funclist>);
  ...
}

open fonksiyonu luaopen_foo paylaşılan bir ortam ile yeni bir tablo oluşturur ve bu tabloyu kendi ortamı olarak ayarlamak için lua_replace kullanır. Ardından, lual_register çağırdığında, oluşturulan tüm yeni fonksiyonlar bu mevcut ortamı devralır.

Verileri diğer modüllerle paylaşmanız gerekmedikçe, ortamı registry üzerinde olmasını daima tercih etmelisiniz. Özellikle, yalnızca modüle görünür başvurular oluşturmak için ortam tablosunu kullanarak referans sistemini kullanabilirsiniz.

Upvalues(Üstdeğerler)

Registry, global değişkenler sunar ve ortamlar modül değişkenleri sunarken, upvalue mekanizması yalnızca belirli bir fonksiyon içinde görünür olan C statik değişkenlerinin eşdeğerini uygular. Lua'da  yeni bir C fonksiyonu oluştururken her zaman, upvalues herhangi bir miktarını onunla ilişkilendirebilirsiniz ; her bir upvalue tek bir Lua değeri tutabilir. Daha sonra, fonksiyon çağrıldığında, sözde-indeksleri kullanarak upvalues'lerinden herhangi birine serbest erişime sahiptir.

upvalues'ler ile bir C fonksiyonun bu ilişkilendirmesine closure diyoruz. Bir C closure'ı, bir Lua closure'ına bir C yaklaşımıdır. Closure'larla ilgili ilginç bir gerçek, aynı fonksiyon kodunu kullanarak
farklı closure'lar oluşturabilmenizdir, tabi farklı upvalues ile.

Basit bir örnek görmek için, C'de bir newCounter fonksiyonu oluşturalım. (Biz zaten bölüm 6.1'de, Lua'da bu aynı fonksiyonu tanımladık). Bu fonksiyon bir fabrikadır: her çağrıldığında yeni bir sayaç fonksiyonu döndürür. Tüm sayaçlar aynı C kodunu paylaşsa da, her biri kendi bağımsız sayacını tutar.
Fabrika fonksiyonu devamdaki gibi:

static int counter(lua_State *L); /* forward declaration */

int newCounter(lua_State *L){
  lua_pushinteger(L, 0);
  lua_pushcclosure(L, &counter, 1);
  return 1;
}

Burada anahtar fonksiyon, lua_pushcclosure, yeni bir closure oluşturur. İkinci argümanı temel fonksiyondur (counter,örnekte) ve üçüncüsü upvalues sayısıdır(1, örnekte). Yeni bir closure oluşturmadan önce, yığına upvalues için ilkleme değerlerini push etmeliyiz. Örneğimizde, 0 sayısını tek upvalue için başlangıç değeri olarak push ediyoruz. Beklendiği gibi, lua_pushcclosure yığın üzerine yeni closure'ı bırakır, bu suretle closure newCounter'ın sonucu olarak döndürülmeye hazır.

Şimdi, counter tanımını görelim:

static int counter(lua_State *L){
  int val = lua_tointeger(L, lua_upvalueindex(1));
  lua_pushinteger(L, ++val); /* yeni değer */
  lua_pushvalue(L, -1); /* onu yedekle*/
  lua_replace(L, lua_upvalueindex(1)); /* upvalue güncelle*/
  return 1; /* yeni değeri döndür */
}

Burada, anahtar fonksiyon lua_upvalueindex (aslında bir makro), bir upvalue 'nin sözde-indeksini üretir. Yine, bu sözde-indeks, yığında yaşamaması dışında herhangi bir yığın-indeksi gibidir. lua_upvalueindex(1) ifadesi, fonksiyonun ilk upvalue indeksini ifade eder. Yani, lua_tointeger çağrısı bir sayı olarak ilk (ve tek) upvalue'nin geçerli değerini alır. Ardından, counter fonksiyonu,  ++val yeni değerini push eder, bunun bir kopyasını oluşturur ve upvalue değerini değiştirmek için kopyalardan birini kullanır. Son olarak, diğer kopyayı dönüş değeri olarak döndürür.

Daha gelişmiş bir örnek olarak, upvalues kullanarak demetleri(tuples) uygulayacağız. Bir tuple, anonim alanlı bir sabit kayıt(record) türüdür; sayısal bir indeksle belirli bir alanı alabilir veya tüm alanları bir kerede alabilirsiniz. Bizim uygulamada, biz upvalues'lerinde onların değerlerini depolayan fonksiyonlar olarak demetleri temsil ederiz. Sayısal bir argümanla çağrıldığında, fonksiyon belirli alanı döndürür. Argümansızn çağrıldığında, tüm alanları döndürür. Aşağıdaki kod tuples kullanımını göstermektedir:

x = tuple.new(10, "hi", {}, 3)
print(x(1)) --> 10
print(x(2)) --> hi
print(x()) --> 10 hi table: 0x8087878 3

C'de, 27.3 listesinde sunulan, t_tuple aynı fonksiyonla tüm tuple'leri temsil ediyoruz. Sayısal argümanlı veya argümansız bir tuple'i çağırabildiğimiz için, t_tuple, isteğe bağlı argümanını almak için luaL_optint kullanır. lual_optint fonksiyonu lual_checkint'e benzer, ancak argüman yoksa sorun yaratmaz; bunun yerine, belirli bir varsayılan değer (0, örnekte) döndürür.

Varolmayan bir upvalue indekslediğimizde, sonuç, tipi LUA_TNONE olan bir sözde-indekstir.
(Geçerli üsttün üstünde bir yığın indeksine eriştiğimizde, bu LUA_TNONE türüyle de sözde-değer elde ederiz.) Bu nedenle, t_tuple fonksiyonumuz, verilen bir upvalue olup olmadığını test etmek için lua_isnone'ı kullanır. Bununla birlikte, asla lua_upvalueindex'i negatif bir indeks ile çağırmamalıyız,
bu nedenle kullanıcı indeksi sağladığında bu durumu kontrol etmeliyiz. luaL_argcheck fonksiyonu, gerekirse bir hata yükselterek, belirli bir koşulu-durumu denetler-kontrol eder.

Tuple'ler oluşturmak için fonksiyon, t_new (ayrıca 27.3 listesinde), önemsİzdİr: argümanları hali hazırda yığında olduğundan, sadece , upvalues olarak bu argümanlar ile t_tuple 'in closure'ını oluşturmak için lua_pushcclosure'ı çağırır. Son olarak, tuplelib dizisi ve luaopen_tuple  fonksiyonu (ayrıca 27.3 Listesinde) bu tek new fonksiyonu ile bir tuple kütüphanesi  oluşturmak için standart kod vardır.

Liste 27.3. tuple uygulaması:

int t_tuple(lua_State *L){
  int op = luaL_optint(L, 1, 0);
  if (op == 0)  { /* no arguments? */
    int i;
    /* push each valid upvalue onto the stack */
    for (i = 1; !lua_isnone(L, lua_upvalueindex(i)); i++)
      lua_pushvalue(L, lua_upvalueindex(i));
    return i - 1; /* number of values in the stack */
  }
  else  { /* get field ’op’ */
    luaL_argcheck(L, 0 < op, 1, "index out of range");
    if (lua_isnone(L, lua_upvalueindex(op)))
      return 0; /* no such field */
    lua_pushvalue(L, lua_upvalueindex(op));
    return 1;
  }
}

int t_new(lua_State *L){
  lua_pushcclosure(L, t_tuple, lua_gettop(L));
  return 1;
}

static const struct luaL_Reg tuplelib[] = {
    {"new", t_new},
    {NULL, NULL}
};

int luaopen_tuple(lua_State *L){
  luaL_register(L, "tuple", tuplelib);
  return 1;
}

Hiç yorum yok:

Yorum Gönder