20 String Kütüphanesi

20 String Kütüphanesi

Ham Lua yorumlayıcının gücü stringleri manipüle etmede oldukça sınırlıdır. Program, string öbekleri oluşturabilir, onları birleştirebilir ve string uzunluklarını elde edebilir. Ancak alt string kısımlarını çıkartamaz veya içeriklerini kurcalayamaz. Lua'da stringleri manipüle etmede tam güç string kütüphanesinden gelir.

string kütüphanesi, string ismiyle bir modül olarak fonksiyonlarını ihraç eder. Lua 5.1'de aynı zamanda string tipinin metotları olarak fonksiyonlarını ihraç eder (bu tipin metatablosu kullanılarak). Böylece örneğin bir stringi büyük harflere çevirmek için string.upper(s) veya s:upper() yazabiliriz. Seçim size kalmış. Lua 5.0 ile gereksiz uyumsuzluklardan kaçınmak için bu kitaptaki çoğu örnekte modül notasyonunu kullanıyorum.

20.1 Temel String Fonksiyonları

string kütüphanesindeki bazı fonksiyonlar oldukça basit: string.len(s), s stringinin uzunluğunu döndürür. string.rep(s,n) ( veya s:rep(n) ), s stringinin n kez tekrarlanmışını döndürür. string.rep("a", 2^20) ile 1 MBlık a string'i(test için misal )  oluşturabilirsiniz. string.lower(s), büyük harfli s'i küçük harfliye çevirip bir kopyasını döndürür; stringdeki diğer tüm karakterler değişmez (string.upper büyük harfliye çevirir.). Tipik bir kullanım olarak, büyüklük dikkate almadan bir stringler serisini sıralamak isterseniz devamdaki gibi bir şey yazabilirsiniz:

table.sort(a, function(a, b)
                 return string.lower(a) < string.lower(b)
              end)

string.upper ve string.lower ikilisi mevcut yerelleştirme ayarını takip eder. Bu şekilde, varsayılanda olan C derleyicisi yerelleştirme ayarıyla çalışıyorsanız(ki o da ingilizcedir) string.upper("üşengeç") ifadesi “üşENGEç” ile sonuçlanır.

string.sub(s,i,j) çağrısı s stringinden i-inciden j-inci karaktere kadar bir parçasını çıkarır. Lua'da, bir stringin ilk karakterinin indeksi 1'dir.  Ayrıca stringin sonundan sayaçlayan negatif indeksleri de kullanabilirsiniz: indeks -1 bir stringdeki son karakteri, -2 bir öncekini vb. gider. Mamafi, string.sub(s,1,j) ( veya s:sub(1,j) ) çağrısı j uzunlukla s stringinin önekini elde eder; string.sub(s,j,-1) (veya basitçe s:sub(j) , son argüman için varsayılan -1),  j-inci karakterden başlayarak string sonekini elde eder; ve string.sub(s,2,-2)  ilk ve son karakterleri kaldırılmış s stringinin kopyasını döndürür:

s = "[koseli parantez ici]"
print(string.sub(s, 2, -2)) --> koseli parantez ici

Lua'da stringlerin değişmez olduğunu unutmayın. string.sub fonksiyonu, Lua'daki herhangi başka bir fonksiyon gibi, bir stringin değerini değiştirmez ama yeni bir string döndürür. yaygın bir hata devamdaki gibi bir şey yazıp:

string.sub(s, 2, -2)

s değerinin değiştirileceğini varsaymaktır. Değişkenin değerini değiştirmek istiyorsanız yeni değeri ona atamanız gerekir:

s = string.sub(s, 2, -2)

string.char ve string.byte fonksiyonları karakterler ve onların içsel sayısal temsilleri arasında dönüştürme yapar. string.char fonksiyonu sıfır veya daha fazla tamsayılar alır, her birini bir karaktere dönüştürür ve tüm bu karakterlerin birleştirildiği bir string  döndürür. string.byte(s, i) fonksiyonu, s stringinin i-inci karakterinin içsel sayısal temsilini döndürür; ikinci argüman isteğe bağlıdır, Mamafi string.byte(s) çağrısı, s'nin ilk (veya tek) karakterinin içsel sayısal temsilini döndürür. Aşağıdaki örneklerde, karakterlerin ASCII cinsinden temsil edildiğini varsayıyoruz:

print(string.char(97))                      --> a
i = 99 print(string.char(i, i + 1, i + 2))  --> cde
print(string.byte("abc"))                   --> 97
print(string.byte("abc", 2))                --> 98
print(string.byte("abc", -1))               --> 99

Son satırda stringin son karakterine erişmek için negatif bir indeks kullandık.

Lua 5.1'de string.byte isteğe bağlı bir üçüncü argüman kabul eder. string.byte(s,i,j) gibi bir çağrı, i ve j (dahil) indeksleri arasındaki tüm karakterlerin sayısal gösterimi ile çoklu değer döndürür:

print(string.byte("abc", 1, 2)) --> 97 98

j için varsayılan değer i, bu nedenle bu argüman olmadan bir çağrı yalnızca i-inci karakteri döndürür, Lua 5.0'daki gibi.  {s:byte(1,-1)} güzel bir deyim, s'deki tüm karakterlerin kodlarıyla bir tablo oluşturur. Bu tabloyu vererek, string.char(unpack(t)) çağrısıyla orjinal stringi yeniden oluşturabiliriz.
Ne yazık ki, bu teknik uzun stringler için çalışmaz (örneğin 2 Kbayt'dan daha uzun), çünkü Lua, bir fonksiyonun döndürebileceği değer miktarına sınır koymuştur.

string.format fonksiyonu,  stringleri biçimlendirmek(tipik olarak çıktı için) için güçlü bir araçtır. biçimleme(format) string'i olarak adlandırılan  ilk parametrede verilen tanımlama devamında değişen sayıda ki argümanların biçimlendirilmiş versiyonunu döndürür. format stringi, standart C'nin printf fonksiyonuna benzer kurallara sahiptir: herbir argümanın biçimlendirilmiş string'de nasıl konumlanacağını kontrol eden özel metin ve direktifler bileşiminden oluşur. direktif, '%' karakteri artı argümanın nasıl biçimleneceğini söyleyen bir harfdir: 'd' ondalık sayı için, 'x' 16lık hex için, 'o' 8lik oktal için, 'f' kayan noktalı sayılar için, 's' stringler için, artı diğer varyasyonlar. '%' ve harf arasına bir direktif , kayan noktalı sayılar için ondalık kısmın miktarı gibi biçimlendirme detaylarını kontrol eden diğer seçenekler dahil edebilir:

print(string.format("pi = %.4f", math.pi))      --> pi = 3.1416
d = 5; m = 11; y = 1990
print(string.format("%02d/%02d/%04d", d, m, y)) --> 05/11/1990
tag, title = "h1", "a title"
print(string.format("<%s>%s</%s>", tag, title, tag))
 --> <h1>a title</h1>

İlk örnekteki %.4f, ondalık noktadan sonra dört basamaklı kayan noktalı sayı anlamına gelir. İkinci örnekte %02d, tam sayının en az iki basamaklı olacağını, olmazsa başı sıfırla doldurulacak anlamına gelir; %2d direktifi, sıfırsız, dolgu olarak boşluk kullanır. Bu direktiflerin tam açıklaması için, Lua başvuru kılavuzuna bakın. Ya da, daha da iyisi, Lua buradaki zorlu iş için standart C kütüphanesini çağırdığından , bir C el kitabına bakın.

20.2 Şablon-Eşleme Fonksiyonları

string kütüphanesindeki en güçlü fonksiyonlar find, match, gsub (Global Substitution-ikame) ve gmatch (Global Match-eşleme) ' dir. Hepsi şablon temellidir.

Diğer birçok script dili aksine, Lua şablon eşleştirme için ne POSIX (regexp) ne de Perl düzenli ifadelerini(regular expressions) kullanır. Bu kararın ana nedeni boyut: POSIX düzenli ifadelerinin tipik bir uygulaması 4000'den fazla kod satırı alır. Bu, tüm Lua standart kütüphanelerinin boyutuyla aynıdır. Buna karşılık, Lua'daki şablon eşleştirme uygulaması 500 satırdan az. Elbette, Lua'da şablon eşleme, komple POSIX uygulamasının yaptığı her şeyi yapamaz. Bununla birlikte Lua'da şablon eşleştirme güçlü bir araçtır ve  zor eşlemeler için standart POSIX uygulamalarının bazı özelliklerini içerir.

string.find fonksiyonu

string.find fonksiyonu verilen string deneği içinde bir şablon için arama yapar. Bir şablonun en basit şekli, yalnızca kopyasıyla eşleşen bir kelimedir. Örneğin, 'hello' şablonu string deneği içindeki alt string "hello" 'ı arayacaktır. find bu şablonu bulduğunda iki değer döndürür: eşleşmenin başladığı indeks ve eşleşmenin bittiği indeks. Bir eşleşme bulamazsa nil döndürür:

s = "hello world"
i, j = string.find(s, "hello")
print(i, j)                     --> 1 5
print(string.sub(s, i, j))      --> hello
print(string.find(s, "world"))  --> 7 11
i, j = string.find(s, "l")
print(i, j)                     --> 3 3
print(string.find(s, "lll"))    --> nil

eşleşme başarılı olduğunda şablonla eşleşen denek string kısmını almak için string.find ile döndürülen değerlerle string.sub'ı çağırabiliriz. (Basit şablonlar için bu, şablonun kendisidir.) 

string.find fonksiyonu isteğe bağlı bir üçüncü parametreye sahiptir: denek stringde aramanın başlatılacağını yeri söyleyen indeks. Bu parametre, verilen şablonun görüldüğü yerlerin tüm indekslerini işlemek istediğimizde kullanışlıdır: bir öncekini bulduğumuz pozisyondan başlayan yeni bir eşleşme ararız tekrarlı olarak.  Örnek olarak aşağıdaki kod, bir stringdeki tüm yenisatırların konumlarıyla bir tablo oluşturur:

local t = {} -- indeksleri depolayacak tablo
local i = 0
while true do
    i = string.find(s, "\n", i + 1) -- sıradaki yenisatırı bul
    if i == nil then break end
    t[#t + 1] = i
end

Daha sonra string.gmatch iteratörünü kullanarak bu tür döngüleri yazmanın daha basit bir yolunu göreceğiz.

string.match fonksiyonu

string.match fonksiyonu string.find fonksiyonuna benzer, aynı şekilde bir stringde bir şablon arar anlamında. Bununla birlikte, şablonu bulduğu konumu döndürmek yerine şablonla eşleşen denek string kısmını döndürür:

print(string.match("hello world", "hello")) --> hello

'hello' gibi sabit pür şablonlar bu fonksiyona hafif gelir. Bir sonraki örnekte olduğu gibi değişken ham şablonlar kullanıldığında gücünü gösterir:

date = "Today is 17/7/1990"
d = string.match(date, "%d+/%d+/%d+")
print(d) --> 17/7/1990

Kısaca, ‘%d+/%d+/%d+’ şablonunun anlamını ve string.match için daha gelişmiş kullanımları ele alacağız.

string.gsub fonksiyonu

string.gsub fonksiyonu üç parametreye sahiptir: bir denek string, bir şablon ve ikame string. denek string içindeki tüm şablon oluşumları için ikam stringi kullanılır basitçe.

s = string.gsub("Lua is cute", "cute", "great")
print(s) --> Lua is great
s = string.gsub("all lii", "l", "x")
print(s) --> axx xii
s = string.gsub("Lua is great", "Sol", "Sun")
print(s) --> Lua is great

opsiyonel bir dördüncü parametre yapılacak ikame sayısını sınırlar:

s = string.gsub("all lii", "l", "x", 1)
print(s) --> axl lii
s = string.gsub("all lii", "l", "x", 2)
print(s) --> axx lii

string.gsub fonksiyonu ikinci bir sonuç olarak kaç ikame yapıldığını döndürür. Örneğin, bir stringdeki boşlukların sayısını saymanın kolay bir yolu:

count = select(2, string.gsub(str, " ", " "))

string.gmatch fonksiyonu

string.gmatch fonksiyonu bir stringdeki bir şablonun tüm oluşumlarını dolaşan(iterasyon yapan) bir fonksiyon döndürür. Örneğin aşağıdaki örnek, verilen bir s stringinin tüm "kelimelerini" toplar:

kelimeler = {}
for kelime in string.gmatch(s, "%a+") do
    kelimeler[#kelimeler + 1] = kelime
end

Kısaca , ‘%a+’ şablonu bir veya daha fazla alfabetik karakter dizisiyle eşleşir (yani kelime). Yani for döngüsü, denek stringin  tüm kelimeleri üzerinde iterasyon yapıp, bunları kelimeler listesinde saklar.

gmatch ve gsub kullanarak,  Lua modülleri aranırken kullanılan require arama stratejini simüle etmek zor değil.

function ara(modulismi, yol)
    modulismi = string.gsub(modulismi, "%.", "/")
    for c in string.gmatch(yol, "[^;]+") do
        local dosyaismi = string.gsub(c, "?", modulismi)
        local dosya = io.open(dosyaismi)
        if dosya then
            dosya:close()
            return dosyaismi
        end
    end
    return nil -- bulunamadı
end

İlk adım, dizin ayırıcısını, örnekte ‘/’ olduğu varsayılmakta, her bir nokta için ikame etmek. (Daha sonra göreceğimiz üzere, bir noktanın bir şablonda özel bir anlamı vardır. Başka anlamları olmayan bir nokta elde etmek için '%.' yazmalıyız.) Daha sonra fonksiyon, yolun tüm bileşenleri üzerinde döngüler; buradaki her bileşen, noktalı virgül olmayan karakterlerin maksimum silsilesi. Her bileşen için, nihai dosya adını elde etmek üzere soru işaretleri ile modül adını değiştirir ve sonra böyle bir dosyanın olup olmadığını kontrol eder. Eğer varsa, fonksiyon dosyayı kapatır ve adını döndürür.

20.3 Şablonlar

Şablonları, karakter sınıflarıyla daha kullanışlı hale getirebilirsiniz. Bir karakter sınıfı bir şablonda ki bir öğedir, ki belli bir kümedeki tüm karakterle eşleşebilir. Örneğin, %d karakter sınıfı tüm rakamlarla eşleşir. Velhasıl, ‘%d%d/%d%d/%d%d%d%d’ şablonuyla gg/aa/yyyy biçiminde bir tarih arayabilirsiniz:

s = "Son kullanma tarihi 30/05/1999"
tarih = "%d%d/%d%d/%d%d%d%d"
print(string.sub(s, string.find(s, tarih))) --> 30/05/1999

Aşağıdaki tabloda tüm karakter sınıfları listelenmiştir:

.   tüm karakterler
%a  harfler
%c  kontrol karakterleri
%d  sayılar
%l  kucuk harfler
%p  noktalama karakterleri
%s  boşluk karakterleri
%u  büyük harfler
%w  alfanümerik karakterler
%x  hex sayılar
%z  0 ile temsil olan karakter

bu sınıfların büyük-harfli notasyonu, aksi anlamda temsil sağlar. mesela, '%A', harf olmayan tüm karakterleri temsil eder('%a' tüm harfler idi) :

print(string.gsub("selam, saga-sola!", "%A", "."))
--> selam..saga.sola. 4


(4, sonuç stringinin bir parçası değildir. gsub'un ikinci sonucudur, toplam ikame sayısı. gsub'ın sonucunun yazdırıldığı diğer örneklerde bu sayacı atlayacağım.).

Sihirli karakterler adı verilen bazı karakterler, bir şablonda kullanıldığında özel anlamlara sahiptir. Sihirli karakterler:

( ) . % + - * ? [ ] ^ $

'%' karakteri, bu sihirli karakterler için bir kaçış sekansı olarak çalışır. Yani '%.' , bir nokta ile eşleşir; '%%' , ’%' karakterinin kendisi ile eşleşir. Sadece sihirli karakterler için değil aynı zamanda alfasayısal olmayan tüm karakterler için de ‘%’ kaçış sekansı kullanabilirsiniz. Şüphe duyduğunuzda, güvenli oynayın ve bir kaçış sekansı koyun.

Lua için şablonlar normal stringlerdir. Diğer stringler gibi aynı kuralları izlerler, özel bir muameleye sahip değiller. sadece şablon fonksiyonları bunları şablon olarak yorumlar ve sadece o zaman ’%' bir kaçış sekansı olarak çalışır. Bir şablon içine tek tırnak alıntısı koymak için, diğer stringler içine tek tırnak alıntısı koymak için kullandığınız aynı teknikleri kullanırsınız; örneğin Lua için kaçış karakteri olan '\' ile tek tıknak alıntısı kaçırılabilir.

Bir karakter-seti, köşeli parantezler arasında tek karakter ve farklı sınıf kombinasyonuyla kendi karakter sınıfınızı yaratmanıza imkan verir.  Örneğin, '[%w_]' karakter-seti alfasayısal karakterler ve alt çizgilerle eşleşir; ’[01]‘ karakter-seti binary sayılarla eşleşir; ve '[%[ % ]]' karakter-seti köşeli parantezlerle eşleşir. Bir metinde tüm ünlü harflerin sayısını saymak için şunu yazabilirsiniz:

unluHarfSayisi = select(2, string.gsub(metin, "[AEIOUaeiou]", ""))

bir karekter-setinde, bir tire ile aralık ayırıcının ilk ve son karakteri yazılarak, karakter aralığı tayin edebilirsiniz. Bu imkanı nadiren kullanıyorum çünkü en kullanışlı aralıklar zaten öntanımlı olarak var; örneğin '[0-9]' ,  '%d' ile aynı ve '[0-9a-fA-F]' , '%x' ile aynı. Ancak, octal bir sayı bulmanız gerekiyorsa, '[01234567]' gibi açık bir listeleme yerine '[0-7]' 'i tercih edebilirsiniz. herhangi bir karekter-setini '^' ile başlatarak aksini elde edebilirsiniz: '[^0-7]' şablonu octal bir sayı OLMAYAN tüm karakterleri bulur ve '[^\n]' yenisatırdan FARKLI tüm karakterlerle eşleşir. Tabi, basit sınıfları büyük-harfli notasyonla da aksini yaptırabileceğimizi hatırlayalım: '%S' , '[^%s]' den daha basit.

Karakter sınıfları, Lua için mevcut yerelleştirme ayarını takip eder. Bu nedenle '[a-z]' sınıfı, '%l' den farklı olabilir. Misal brezilya için yerelleştirme ayarında, ikinci form  ‘c¸’ ve ‘a~’ gibi harfleri
içerir. Aksini yapmak için güçlü bir nedeniniz yoksa her zaman ikinci formu kullanmalısınız: daha basit, daha taşınabilir ve bir parça daha efektif.

şablonları, opsiyonel kısımlar ve yinelemeler için belirteçlerle(modifier) daha kullanışlı hale getirebilirsiniz. Lua 'da şablonlar dört belirteç sunar:

+     1 veya daha fazla yineleme
*      0 veya daha fazla yineleme
-      yine 0 veya daha fazla yineleme
?      isteğe bağlı (0 veya 1 oluşum)

'+' belirteci, orijinal sınıfın bir veya daha fazla karakteriyle eşleşir. Daima şablonla eşleşen en uzun seriyi alacaktır. Örneğin '%a+' şablonu bir veya daha fazla harf veya bir kelime anlamına gelir:

print(string.gsub("bir, ve iki; ve uc", "%a+", "kelime"))
--> kelime, kelime kelime; kelime kelime

'%d+' şablonu bir veya daha fazla sayıyla eşleşir (tamsayı):

print(string.match("1298 nolu ogrenci", "%d+")) --> 1298

 '*' belirteci  '+'  'e benzer ama aynı zamanda sınıfın karakterlerin sıfır oluşunu kabul eder.
Tipik bir kullanım, bir şablonun parçaları arasındaki isteğe bağlı boşlukları eşleştirmek içindir. Örneğin, () veya ( ) gibi boş parantez çiftlerini eşleştirmek için '%(%s*%)' şablonunu  kullanırsınız: '%s*' şablonu sıfır veya daha fazla boşlukla eşleşir. (Parantezler de aynı zamanda bir şablonda özel anlam taşır, bu yüzden onları '%' ile kaçırmalıyız.)  Başka bir örnek olarak,  '[_%a][_%w]*' şablonu bir Lua programındaki tanımlayıcılarıyla(değişken ismi) eşleşir: harf veya alt çizgi ile başlayan , ardından sıfır veya daha fazla alt çizgi veya alfanümerik karakterler serisi.

'-' belirteci yine '*' gibi orijinal sınıfın karakterlerinin sıfır veya daha fazla oluşumuyla eşleşir. Bununla birlikte, en uzun seri eşleştirmesi yerine en kısa seriyle eşleşir. Bazen, '*' ve '-' arasında bir fark yoktur ama genelde oldukça farklı sonuçlar sunarlar. Örneğin, '[_%a][_%w]-' şablonuyla bir tanımlayıcı bulmaya çalışırsanız yalnızca ilk harfini bulacaksınız çünkü '[_%w]-' her zaman boş seriyle eşleşecektir.  Öte yandan, bir C programındaki yorumları bulmak istediğinizi varsayalım. Birçok kişinin ilk girişimi '/%*.*%*/' olacaktır(yani uygun kaçışlarla yazılmış, '/*' devamında herhangi bir karakter serisi ardından bir "*/"). Ancak, '.*' olabildiğince uzayacağı için, programda ilk "/*" sadece sonuncu "*/" ile kapanacak:

test = "int x; /* x */ int y; /* y */"
print(string.gsub(test, "/%*.*%*/", "<YORUM>"))
--> int x; <YORUM>

Bunun yerine, '-' şablonu  istenen sonucu elde edebileceğiniz, ilk "*/" 'i bulup en az uzamayı sağlayacak:

test = "int x; /* x */ int y; /* y */"
print(string.gsub(test, "/%*.-%*/", "<YORUM>"))
--> int x; <YORUM> int y; <YORUM>

Son belirteç '?', isteğe bağlı karakterle eşleşir. Örneğin, bir metinde isteğe bağlı pozitif negatif işareti içerebilecek bir sayı bulmak istediğimizi varsayalım. '[+-]?%d+' şablonu bu işi yapar, "-12", "23" ve "+1009" gibi rakamlarla eşleşme sağlar. '[+-]', hem '+' hem de '-' işareti ile eşleşen bir karakter sınıfıdır; devamındaki '?' bu işaretin isteğe bağlı olduğunu belirtir.

Diğer bazı sistemlerin aksine, Lua'da bir belirtec yalnızca bir karakter sınıfına uygulanabilir; bir belirteç altında şablonları gruplama imkanı yoktur. Örneğin, isteğe bağlı bir kelimeyle eşleşen bir şablon yoktur (kelime sadece bir harfe sahip olmadığı sürece). Genelde, bu bölümün sonunda göreceğimiz üzere bazı gelişmiş teknikler kullanarak bu sınırlamayı atlatabilirsiniz.

Bir şablon  '^' ile başlarsa, yalnızca denek stringin başında eşleşme yapar. Benzer şekilde, '$' ile biterse yalnızca denek stringin sonunda eşleşme yapar. Bu işaretler hem aradığınız şablonları daraltma hem de sağlama alma için kullanılabilir. Örneğin şu test:

if string.find(s, "^%d") then ...

s stringinin bir sayı ile başlayıp başlamadığını kontrol eder , ve şu test:

if string.find(s, "^[+-]?%d+$") then ...

Bu stringin öncü veya artçı karakter olmadan bir tam sayıyı temsil edip etmediğini kontrol eder. Bir şablondaki diğer bir öğe '%b', birbirini denkleme stringlerini eşler.  x ve y'nin iki farklı karakter olduğu '%bxy' olarak yazılan bu öğe; x'e bir açılış karakteri( parantez açma gibi) olarak muamele edilir ve y'de kapanış(parantez kapama gibi) karakteri olarak muamele edilir. Örneğin '%b()' şablonu, '(' ile başlayan ve birbirini denkleyen ')' ile biten string parçalarını eşler:

s = "bir (parantez (içi)) satir"
print(string.gsub(s, "%b()", "")) --> bir satir

Tipik olarak bu şablon, ‘%b()’, ‘%b[]’, ‘%b{}’ veya ‘%b<>’ şeklinde kullanılır ancak tüm karakterler ayırıcı olarak kullanabilirsiniz.

20.4 Çekim

Çekim mekanizması, daha ileri kullanım için şablon parçalarıyla eşleşen denek string parçalarını mıknatıs gibi çekmek için bir şablon sağlar. Parantezler arasında çekmek istediğiniz şablonun parçaları yazılarak bir çekim belirtirsiniz.

Bir şablon bir çekime sahip olduğunda string.match fonksiyonu, ayrı bir sonuç olarak yakalanan her değeri  döndürür; başka bir deyişle, stringi yakalanan parçalarına böler: ( Lua 5.0'de bu işi string.find yapıyordu.) .

pair = "ismi = Ahmet"
key, value = string.match(pair, "(%a+)%s*=%s*(%a+)")
print(key, value) --> ismi Ahmet

'%a+' şablonu boş olmayan harf dizisini belirtir; '%s*' şablonu boş alanlar dizisini belirtir. Bu şekilde, yukarıdaki örnekteki şablonun bütünü, bir harf dizisi ardından boş alan dizisi ardından '=', tekrar devamda boşluklar artı diğer harf dizisini belirtir. Her iki harf dizisi de parantez ile çevrelenmiş şablonlarına sahiptir ki bir eşleşmede çekilir olsunlar. Aşağıdaki benzer bir örnek:

bugun = "Tarih 17/7/1990"
g, a, y = string.match(bugun, "(%d+)/(%d+)/(%d+)")
print(g, a, y) --> 17 7 1990

şablonun kendisinde çekimler kullanabiliriz. Bir şablonda, d'nin tek sayı olduğu '%d' gibi bir öğe, yalnızca d-inci çekiminin kopysıyla eşleşir. Tipik bir kullanım olarak, bir stringin içinde tek veya çift tırnak işaretleri arasında tutulan bir alt stringi bulmak istediğinizi varsayalım.  ‘["’].-["’]’ gibi bir şablon deneyebilirsiniz yani bir tırnak devamında herhangi bir şey ve devamında diğer bir tırnak; ancak “sen’de haklisin” gibi dizelerle sorunlarımız olurdu. Bu sorunu çözmek için ilk tırnağı yakalayabilir ve ikincisini belirtmek için onu şöyle kullanabilirsiniz:

s = [[dedi ki: "sen’de haklisin"!]]
q, quotedPart = string.match(s, '(["’])(.-)%1')
print(quotedPart) --> sen’de haklisin
print(q) --> "

İlk çekim tırnak karakterinin kendisi ve ikinci çekim tırnağın içeriğidir ('.-' alt string eşlemesi).

Benzer bir örnek, Lua da uzun stringlerle eşleşen şablon:

%[(=*)%[(.-)%]%1%]

açık köşeli parantez ardından sıfır veya daha fazla eşittir işareti ardından başka bir açık köşeli parantez ardından herhangi bir şey (string içeriği) ardından bir kapalı köşeli parantez ardından aynı sayıda eşittir işareti ardından diğer  kapalı köşeli parantez ile eşleşecek:

p = "%[(=*)%[(.-)%]%1%]"
s = "a = [=[[[ biseyler ]] ]==] ]=]; print(a)"
print(string.match(s, p)) --> = [[ biseyler ]] ]==]

İlk çekim eşittir işaretleri dizisi (bu örnekte sadece bir tane); ikincisi string içeriğidir.

çekilen değerlerin üçüncü kullanımı, gsub'un ikame stringindedir. şablon gibi aynı şekilde ikame stringi, değiştirme yapıldığı zaman ilgili çekimleri değiştirilen '%d' gibi öğeleri içerebilir. Özellikle, "%0" öğesi tüm eşleşmeyi değiştirir. (Bu arada, ikame stringdeki bir '%', "%%" şeklide kaçış sekansıyla olmalı. Örnek olarak aşağıdaki komut bir stringdeki her harfi çoğaltır, kopyaları arasına bir tire ile:

print(string.gsub("hello Lua!", "%a", "%0-%0"))
--> h-he-el-ll-lo-o L-Lu-ua-a!

Devamdaki bitişik karakterlere yer değiştirir:

print(string.gsub("hello Lua", "(.)(.)", "%2%1")) --> ehll ouLa

Daha yararlı bir örnek olarak, devamdaki gibi LaTeX tarzında yazılmış komutlu string alarak:

\komut{bir metin}

bunları XML stili biçimine dönüştüren:

<komut>bir metin</komut>

basit bir biçim dönüştürücü yazalım. İç içe geçmiş komutlara izin vermiyorsak aşağıdaki satır bu işi yapar:

s = string.gsub(s, "\\(%a+){(.-)}", "<%1>%2</%1>")

Örneğin s stringi şu ise

simdiki \\alinti{gorev} bicim \\oge{degisimi}.

gsub için bu çağrı s 'i şuna dönüştürecek:

simdiki <alinti>gorev</alinti> bicim <oge>degisimi</oge>.

(sonraki bölümde iç içe geçmiş komutların nasıl işleneceğini göreceğiz.).

Bir başka yararlı örnek, bir stringin kırpılması:

function kirpma(s)
    return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
end

şablon formatlarının akılcı kullanımına dikkat. İki çapa ( '^' ve '$'), tüm stringi almamızı sağlar.
'.-' mümkün olduğunca az uzanma sağlamaya çalıştığı için iki şablon '%s*', uçlar arasındaki tüm boşluklarla eşleşir. Ayrıca, gsub iki değer döndürdüğü için ekstra sonucu (miktar) atmak için ekstra parantezler kullandığımızı hatırlayın.

20.5 İkameler

Bir string yerine, string.gsub'a üçüncü argüman olarak bir fonksiyon veya bir tablo kullanabiliriz.
Bir fonksiyonla çağrıldığında string.gsub, bir eşleşme bulduğu her seferde fonksiyonu çağırır; her çağrıdaki argümanlar çekimlerdir ve fonksiyonun döndürdüğü değer ikame stringi olarak kullanılandır. Bir tablo ile çağrıldığında string.gsub t çekim olarak kullanılan tablo anahtarına bakar ve bu anahtara ilişkilendirilmiş değer ikame stringi olarak kullanılır. Tablo bu anahtara sahip değilse, gsub bu eşleşmeyi değiştirmez.

İlk örnek olarak, aşağıdaki fonksiyon değişken yerleştirme uygular: bir stringde $degiskenismi 'nin her oluşumu için global değişken degiskenismi'nin değeri ikame eder:

function yerlestirme(s)
    return (string.gsub(s, "$(%w+)", _G))
end

isim = "Lua"; durum = "harika"
print(yerlestirme("$isim bir $durum, öyle değil mi?"))
--> Lua bir harika, öyle değil mi?

'$(%w+)'  (dolar işareti devamında bir isim) ile her bir eşleşme için gsub, çekilen ismi global tablo _G'de arar; sonuç, eşleşmede yerini alır. Tablo anahtarı olmadığında ikame olmaz:

print(yerlestirme("$digerisim bir $status, öyle değil mi?"))
--> $digerisim bir harika, öyle değil mi?

Verilen değişkenlerin string değerlerine sahip olup olmadığından emin değilseniz değerlere  tostring 'i uygulamak isteyebilirsiniz. Bu durumda, ikame değeri olarak bir fonksiyon kullanabilirsiniz:

function yerlestirme(s)
    return (string.gsub(s, "$(%w+)", function(n)
                                         return tostring(_G[n])
                                     end))
end

print(yerlestirme("print = $print; a = $a"))
--> print = function: 0x8050ce0; a = nil

Şimdi, ‘$(%w+)’ ile her eşleşme için gsub, argüman olarak çekilen isimle verilen fonksiyonu çağırır ve sonuç eşleşmede ikame edilir. fonksiyon nil döndürürse ikame yoktur. (Bu durum bu örnekte olamaz çünkü tostring asla nil döndürmez.). 

Bu son örnekle, önceki bölümdeki biçim dönüştürücüsüne dönelim. Yine,  LaTeX stili komutları (\ornek{metin}) XML stiline (<ornek>metin</ornek>) dönüştürmek istiyoruz ancak bu sefer iç içe geçmiş komutlara izin veriyoruz. Sıradaki fonksiyon bu işi yapmak için yineleme kullanır:

function toxml(s)
    s = string.gsub(s, "\\(%a+)(%b{})", function(tag, body)
            body = string.sub(body, 2, -2) -- barikatları kaldır
            body = toxml(body) -- içiçe komutları işle
            return string.format("<%s>%s</%s>", tag, body, tag)
        end)
    return s
end

print(toxml("\\title{The \\bold{big} example}"))
--> <title>The <bold>big</bold> example</title>

URL kodlama

Bir sonraki örneğimiz için,  bir URL'e parametre göndermek için HTTP tarafından kullanılan kodlama olan URL encoding kullanıyoruz. Bu kodlama, xx'in karakterin onaltılık gösterimde temsil edildiği "%xx" olarak özel karakterleri ('=' , '&' ve '+’ gibi) kodlar. Sonra, boşlukları '+'  'a çevirir. Örneğin,  "a+b = c" stringini “a%2Bb+%3D+c” olarak kodlar. Nihayetinde her bir parametrenin ismini ve  parametre değerini araya '=' ile yazdırır ve tüm ortaya çıkan ismi=değer çiftleri arasına & işareti ekler . Örneğin değerler:

isim = "al"; sorgu = "a+b = c"; cevap="evet veya hayir"

ise şu şekilde

“isim=al&sorgu=a%2Bb+%3D+c&cevap=evet+veya+hayir” kodlanırlar.

Şimdi, bu URL'yi çözümlemek ve her değeri karşılık gelen ismiyle indekslenmiş bir tabloda saklamak istediğimizi varsayalım. Aşağıdaki fonksiyon temel kod çözümlemesi yapar:

function unescape(s)
    s = string.gsub(s, "+", " ")
    s = string.gsub(s, "%%(%x%x)", function(h)
            return string.char(tonumber(h, 16))
        end)
    return s
end

İlk deyim, stringdeki her bir ‘+’ değerini bir boşluğa çevirir. İkinci gsub, önünde '%' ile tüm iki haneli onaltılık sayıyla eşleşir ve her eşleşme için anonim bir fonksiyon çağırır. Bu fonksiyon onaltılık rakamı bir sayıya dönüştürür(tonumber, 16 tabanıyla) ve karşılık gelen karakteri (string.char) döndürür. Örneğin

print(unescape("a%2Bb+%3D+c")) --> a+b = c

isim=değer çiftlerini çözümlemek için gmatch kullanırız. Hem isimler hem de değerler '&' veya '=' içeremediğinden onları '[^&=]+' şablonuyla eşleştirebiliriz:

cgi = {}
function cozumle(s)
    for isim, deger in string.gmatch(s, "([^&=]+)=([^&=]+)") do
        isim = unescape(isim)
        deger = unescape(deger)
        cgi[isim] = deger
    end
end

gmatch çağrısı, isim=deger formundaki tüm çiftlerle eşleştirir. Her çift için iteratör karşılık gelen çekimleri (eşleşme stringdeki parantezlerle işaretlenmiş olarak) isim ve değer için değerler olarak döndürür. Döngü gövdesi, her iki stringde de unescape'i çağırır ve cgi tablosunda çifti depolar.

İlgili kodlamayı yazmak da kolay. İlk olarak, escape fonksiyonunu yazıyoruz; bu fonksiyon tüm özel karakterleri ‘%’ ardından onaltılık karakter kodu olarak kodlar (format seçeneği, “%02X”, Dolgu için 0 kullanarak iki haneli bir onaltılık sayı meydana getirir) ve daha sonra boşlukları ‘+’ olarak değiştirir:

function escape(s)
    s = string.gsub(s, "[&=+%%%c]", function(c)
            return string.format("%%%02X", string.byte(c))
        end)
    s = string.gsub(s, " ", "+")
    return s
end

encode fonksiyonu çözümlemek üzere tabloda dolaşarak sonuç stringini oluşturur:

function encode(t)
    local b = {}
    for k, v in pairs(t) do
        b[#b + 1] = (escape(k) .. "=" .. escape(v))
    end
    return table.concat(b, "&")
end

t = {name = "al", query = "a+b = c", q = "yes or no"}
print(encode(t)) --> q=yes+or+no&query=a%2Bb+%3D+c&name=al

Sekmeyi büyütme

‘()’ gibi boş bir çekimin Lua'da özel bir anlamı vardır. Bu şablon bir şey çekmek yerine (oldukça yararsız bir görev),  denek stringdeki konumu bir sayı olarak çeker:

print(string.match("merhaba", "()rh()")) --> 3 5

(Bu örneğin sonucunun string.find'den elde ettiğiniz şeyle aynı olmadığını unutmayın çünkü ikinci boş çekimin konumu eşleşmeden sonrakidir.).

Boş çekimleri kullanımının güzel bir örneği, bir stringdeki sekmeleri(tab) genişletmek içindir:

function expandTabs(s, tab)
    tab = tab or 8 -- sekme "büyüklüğü" (varsayılan 8)
    local corr = 0
    s = string.gsub(s, "()\t", function(p)
            local sp = tab - (p - 1 + corr) % tab
            corr = corr - 1 + sp
            return string.rep(" ", sp)
        end)
    return s
end

gsub şablonu stringdeki tüm sekmelerle eşleşir ve konumlarını yakalar. Her sekme için iç fonksiyon bir sekmenin çoklu sekmede bir sütuna varmak için gereken boşluk miktarını hesaplamak için bu konumu kullanır: sıfıra görece oluşturmak için konumdan bir çıkarır ve önceki sekmeleri telafi etmek için corr 'u ekler (her bir sekmenin genişlemesi, bir sonraki sekmenin konumunu etkiler). Daha sonra bir sonraki sekme için kullanılacak düzeltmeyi günceller: kaldırılan sekme için eksi bir, eklenen boşluklar için artı sp. Son olarak özgün boşluk miktarını döndürür.

Sadece tamlık adına, boşlukları sekmelere dönüştürerek bu işlemi nasıl tersine çevireceğimizi görelim. İlk yaklaşım, pozisyonları manipüle etmek için boş çekimlerin kullanımını da içerebilir ancak daha basit bir çözüm var. Her sekizinci karakterde stringe bir işaret ekliyoruz. Ardından, işaretin öncesinde boşlukların bulunduğu yerde bir sekmeyle onu değiştiririz:

function unexpandTabs(s, tab)
    tab = tab or 8
    s = expandTabs(s)
    local pat = string.rep(".", tab)
    s = string.gsub(s, pat, "%0\1")
    s = string.gsub(s, " +\1", "\t")
    s = string.gsub(s, "\1", "")
    return s
end

fonksiyon, önceki sekmeleri kaldırmak için stringi genişleterek başlar. Ardından, sekme karakterlerinin tüm serisiyle eşleşme için yardımcı bir şablon hesaplar ve her bir sekme karakterinden sonra bir işaret (kontrol karakteri \1) eklemek için bu şablonu kullanır. Daha sonra  boşluklar serisi ardından işaret olan hepsi için bir sekme ikame eder. Son olarak, soldaki işaretleri kaldırır (boşluklardan önce gelmemiş olanlar).

20.6 Püf noktalar

şablon eşleme, stringleri manipüle etmek için güçlü bir araçtır. string.gsub 'e sadece birkaç çağrı ile birçok karmaşık işlemi gerçekleştirebilirsiniz. Tabi, herhangi bir güçte olduğu gibi dikkatli bir şekilde kullanmanız gerekir.

şablon eşleme gerçek bir ayrıştırıcı için bir alternatif değildir. Hızlı ve kirli programlar için, kaynak kodunda yararlı manipülasyonlar yapabilirsiniz ancak kaliteli bir ürün oluşturmak zordur. İyi bir örnek olarak, bir C programındaki yorumlarla eşleştirme için kullandığımız şu şablonu düşünün: ‘/%*.-%*/’ . Programınızın “/*” içeren bir string öbeğine sahipse yanlış bir sonuç elde edebilirsiniz:

test = [[char s[] = "a /* here"; /* a tricky string */]]
print(string.gsub(test, "/%*.-%*/", "<COMMENT>"))
--> char s[] = "a <COMMENT>

Bu tür içeriğe sahip stringler nadirdir ve kendi kullanımınız için bu şablon muhtemelen işinizi görecektir. Ancak böyle bir açıkla programınızın dağıtımını yapmamalısınız.

Genellikle, şablon eşleme Lua programları için yeterince etkilidir: Pentium 333 MHz (eski bir makine), 200K karakter (30K kelime) içeren bir metindeki tüm kelimeleri eşleme saniyenin onda birinden daha az sürer. Tabi tedbirler alabilirsiniz. Daima mümkün olduğunca belirgin şablonlar oluşturmalısınız; gevşek şablonlar belirgin olanlardan daha yavaştır. ekstrem bir örnek ‘(.-)%$’, ilk dolar işaretine kadar bir stringdeki tüm metni almak için. denek stringi bir dolar işaretine sahipse her şey yolunda gider; ancak stringin herhangi bir dolar işareti içermediğini varsayalım. Algoritma ilk önce stringin ilk konumundan başlayarak şablonla eşleştirmeye çalışacak. Bir dolar bulmak için tüm stringi dolaşır. string sonuna ulaşıldığında şablon stringin ilk konumu için başarısız olur. Ardından, algoritma, stringin ikinci pozisyonundan başlayacak, şablonun eşleşmediğini keşfetmek için tüm aramayı tekrar yapacaktır; ve böyle devam edecek. Bu, 200k karakterli bir string için Pentium 333 MHz'de üç saatten fazla süren kuadratik bir zaman(O(n^2)) alacaktır. Bu sorunu, stringin ilk konumuna şablonu bağlamayla bu problemi düzeltebilirsiniz, '^(.-)%$' ile. bağlayıcı, algoritmaya ilk konumda eşleşme bulamazsa aramayı durdurmasını söyler. bağlayıcı ile şablon saniyenin onda birinden daha az çalışır.

Boş şablonlara, yani boş stringle eşleşen şablonlara dikkat aynı zamanda. Örneğin, '%a*' gibi bir şablonla isimleri eşleştirmeye çalışırsanız her yerde isimler bulacaksınız:

i, j = string.find(";$% **#$hello13", "%a*")
print(i, j) --> 1 0

Bu örnekte string.find çağrısı doğru şekilde string başında boş harfler serisi bulur .

'-' belirteci ile başlayan veya biten bir şablon yazmak asla mantıklı değildir çünkü sadece boş stringle eşleşecek. Bu belirteç her zaman genişlemesini bağlayacak etrafta bir şeylere ihtiyaç duyar. Benzer şekilde bir şablon '.*' içeriyorsa sıkıntılı çünkü bu oluşturucu sizin niyetinizden çok daha fazla  genişleyebilir.

Bazen, bir şablon oluşturmak için Lua'nın kendisini kullanmak yararlıdır. Biz zaten boşlukları sekmelere dönüştürmek için fonksiyonumuzda bu numarayı kullandık. Başka bir örnek olarak, bir metinde uzun satırları nasıl bulabileceğimizi nasıl bulacağımıza bakalım, misal 70'den fazla karakterli satır. Uzun bir satır , yenisatırdan farklı 70 veya daha fazla karakter serisidir. karakter sınıfı '[^\n]' ile yenisatırdan farklı tekli karakter eşleştirmesi yapabiliriz . Böylece,  bu, devamında sıfır  veya daha fazla karakter bulunan bir karakter için şablonu 70 kez tekrarlatır. El yordamıyla bu şablonu yazmak yerine string.rep ile onu oluşturabiliriz:

pattern = string.rep("[^\n]", 70) .. "[^\n]*"

Başka bir örnek olarak, büyük küçük harf duyarlı bir arama yapmak istediğinizi varsayalım. Bunu yapmanın bir yolu, '[xX]' sınıfı için yani orijinal harfin hem küçük hem de büyük versiyonunu içeren şablon içindeki tüm x harflerini değiştirmektir . Bu dönüşümü bir fonksiyonla otomatikleştirebiliriz:

function nocase(s)
    s = string.gsub(s, "%a", function(c)
            return "[" .. string.lower(c) .. string.upper(c) .. "]"
        end)
    return s
end

print(nocase("Hi there!")) --> [hH][iI] [tT][hH][eE][rR][eE]!

Bazen, sihirli herhangi bir karakter olmadan s1'in herbir yalın oluşunu s2'e çevirmek istersiniz . s1 ve s2 stringleri öbekler ise stringleri yazarken sihirli karakterlere uygun kaçışlar ekleyebilirsiniz. Ancak bu stringler değişken değerli ise kaçışları sizin için koymak için diğer gsub 'ı kullanabilirsiniz:

s1 = string.gsub(s1, "(%W)", "%%%1")
s2 = string.gsub(s2, "%%", "%%%%")

Arama stringinde, alfasayısal olmayan tüm karakterleri kaçırıyoruz (bunun için büyük ‘W’). İkame stringinde sadece '%' i kaçırıyoruz. 

şablon eşleştirmesi için bir başka yararlı teknik, gerçek işten önce denek stringini önişlemedir.
bir alıntı stringinin  çift tırnak(‘"’ ) ile başladığı ve sona erdiği bir metindeki tüm alıntı stringlerini büyük harfe çevirmek istediğimizi varsayalım ancak tırnak kaçışları içerebilir (“\"”):

Tipik stringimiz: "Bu \"harika\"!".

Bu gibi durumlarda işleme yaklaşımımız, sorunlu seriyi başka bir şeye kodlayacak olan metne önişlem uygulamaktır. Örneğin,  “\"” 'i "\1" olarak kodlayabiliriz. Ancak orijinal metin hazırda "\1" içeriyorsa başımız belada demektir. Kodlama yapma ve bu sorunu önlemenin kolay bir yolu, tüm “\x” serilerini “\ddd” olarak kodlamaktır, ddd burada x karakterinin ondalık gösterimidir:

function code (s)
    return (string.gsub(s, "\\(.)", function (x)
              return string.format("\\%03d", string.byte(x))
            end))
end

artık, kodlanmış stringdeki tüm “\ddd“ serileri kodlamadan gelmeli çünkü orijinal stringdeki tüm ”\ddd" 'ler kodlandı. böylece  kod çözümleme kolay bir iş:

function decode(s)
    return (string.gsub(s, "\\(%d%d%d)", function(d)
                return "\\" .. string.char(d)
            end))
end

Şimdi görevimizi tamamlayabiliriz. kodlanmış string herhangi alıntı kaçışı("\"") içermediğinden alıntı stringlerini basitçe '".-"' ile arayabiliriz:

s = [[follows a typical string: "This is \"great\"!".]]
s = code(s)
s = string.gsub(s, '".-"', string.upper)
s = decode(s)
print(s) --> follows a typical string: "THIS IS \"GREAT\"!".

veya daha küçük bir notasyonla,

print(decode(string.gsub(code(s), '".-"', string.upper)))

1 yorum:

  1. Ustam harika bir yazı, öğreti VAROL.. Lütfen devam ettiğini söyle, yarım kalmış gibi. Teşekkürler..

    YanıtlaSil