Digital Mars D forumunun şu sıralardaki en ateşli konusu Unicode'un tartışıldığı ve topluca öğrenildiği :) "VLERange: a range in between BidirectionalRange and RandomAccessRange" başlıklı konu.
Konu, Andrei'nin "Variable Length Encoding aralığı"na (değişken uzunluklu kodlamalara uygun aralık) duyduğu ihtiyaçla başladı. Bildiğimiz gibi, D'nin string ve wstring türleri değişken uzunluklu kodlardan oluşuyorlar: string'in ve wstring'in içerdiği Unicode karakterleri 1, 2, veya daha fazla kod biriminden oluşur. Örneğin "gğ"c dizgisindeki g tek kod biriminden, ğ ise iki kod biriminden oluşur.
O yüzden de indeksle erişildiğinde umduğumuz gibi harflere değil, kod birimlerine erişiliyor. Geçende burada açtığım bir konuda bir string'in harflerinde ilerlemek için stride()'ın en uygun olduğunu düşündüğümü söylemiştim:
foreach (karakter; stride(dizgi, 1)) {
writeln(" ", karakter);
}
İşte VLERange de stride gibi çalışmak istiyor, ama indeksle erişirken aynı sorunlara sahip oluyor.
Unicode'dan kendi anladıklarımı pekiştirmek için bir özet yazacağım. UYARI: Unicode belgelerinde de geçen "karakter" farklı bağlamlarda farklı anlamlarda kullanılan bir sözcük. Programcıların çoğu için "harf" anlamına gelse de, Unicode'un gözünde "belirli bir kod atanmış olan şey" anlamında kullanılıyor. "Harf" dediğimiz ise birden fazla Unicode karakterinden oluşabiliyor. Yani "karakter", programlama dillerinde kullandığımız karakter değil. :)
Unicode, dünya yazı sistemlerinde kullanılan bütün harflerle, işaretlerle, vs. ilgilenen bir sistem. Temel kavram şunlar:
Kod noktası (Code point) Unicode her karaktere evrensel bir kod veriyor. Örneğin ğ'nin kodu U+011F (yani onaltılı olarak 0x11f (256 + 16 + 15 = 287 değeri))
Birleştirme (combining) Unicode, aksan gibi birleştirici karakterlerin kendilerinden önceki karakterlerle nasıl birleştirildiklerini de belirliyor. Örneğin ğ'nin iki gösterimi vardır: ğ'nin kendisi olan U+011F, veya "g ile birlştirilmiş 'birleştirici breve'" olarak. Ortamın Unicode'u ne kadar desteklediğine bağlı olarak, şu dizgilerin ikisinin de ğ olarak görünmesi gerekir:
import std.stdio;
void main()
{
auto yumuşak_g = "\u011f";
auto g_ve_breve = "g\u0306";
writeln(yumuşak_g);
writeln(g_ve_breve);
}
(Not: Aslında benim konsolumda ğ ve ğ olarak görünüyorlar ama yine de beklentimin doğru olduğunu düşünüyorum. Sanırım bu konuda fontların da etkisi var, çünkü birleştirme karakterlerinin (aksanların) bir önceki karakter üzerine çizilmeleri aslında onların becerisi.)
İm (Grapheme) Unicode, birleştirilmiş olan karakterlere "grapheme" ismini veriyor. Yazı sisteminde sonuçta tek bir birim olarak görüntülenen ve bizim "harf" veya "işaret" dediğimiz şeyler, Unicode'un dilinde "grapheme." Yani bunlar insanların doğal beklentilerini oluşturan şeyler. Bu sayfada gördüğünüz ğ, arka planda nasıl kodlandığından ve nasıl oluşturulduğundan bağımsız olarak bir "grapheme"dir.
Kod birimi (Code unit) Unicode "kod noktaları"nın kodlamalarını da UTF denen düzende belirliyor. UTF kodlamalarının yapı taşlarını kod birimleri oluşturuyor. Örneğin ğ UTF-8'de 0xc4 ve 0x9f baytları olarak, UTF-16'da 0x011f olarak, ve UTF-32'de de 0x0000011f olarak kodlanıyor.
Harf Unicode bu kavrama karışmıyor. Harf olabilmek için bir yazı sistemine (alfabeye) bağlı olmak gerekir. Harflerin küçük ve büyük halleri ve nasıl sıralandıkları yazı sisteminin işidir. Örneğin i'nin büyüğünün alfabeye göre I veya İ olması gerektiğini çok iyi biliyoruz.
Şimdi bütün bu hikayeyi en alt düzeyden en üst düzeye doğru tekrar sıralayacağım. Yukarıda yazdıklarıma ek olan fazla bir şey yok; değişik sıradalar:
-
Kod birimi (code unit): D dili bize üç tane UTF kodlaması sunar.
-
Kod noktası (code point): Belirli bir kodlamanın kodları çözüldükten sonra kod noktalarına geçilir. Yani kod noktaları kod birimlerinden oluşurlar. Aslında bu UTF kodlamasını görüntüleyen ortamın becerisidir. 0xc4 ve 0x9f baytlarını ğ olarak gösteren örneğin benim konsolumdur. Örneğin şu ğ içeren bir dizgi olmadığı halde, konsol tarafından ğ görüntülenir:
write(cast(char)0xc4);
write(cast(char)0x9f);
D'nin dchar türü, Türk alfabesinin de aralarında bulunduğu çok sayıdaki yazı sisteminin kod noktalarını karşılar. O yüzden dchar ve dstring kullandığımızda sorunlarımızın çözüldüğünü sanıyoruz. Çok büyük ölçüde doğrudur ama yeterli değildir. (Bakınız bir sonraki madde.)
- İm (grapheme): İmler, bir veya daha fazla kod noktasından oluşur. Denis Spir ismindeki birisi şu sıralarda bu sorunu çözen bir sınıf yazıyor. Onun sınıfı kullanıldığında "ğ" dizgisi ile "g"~"̆" dizgisi eşit kabul edilecekler. Bu da zaten Unicode'un temel beri beklentisidir: birleştirilmiş veya ayrılmış olsalar da aynı ime karşılık gelen farklı kod noktası dizileri olabilir.
Yukarıda bahsettiğim forum konusunda tartışanların bazıları yazı sistemi konularının da bu düzeyde halledilmeleri gerektiğini sanıyorlar ama yanılıyorlar. :) (Bakınız bir sonraki madde.)
- Harf, noktalama işareti, vs.: Bu düzey yazı sistemleri ile ilgili olan kavramları içerir. Küçük ve büyük harfler, sıralama, vs. buradadır... trileri kütüphanesi bu düzeye odaklanır ama ne yazık ki "grapheme" konusunu tamamen gözardı eder.
Durum bu... :) Ama o kadar da kötü değil. dchar ve dstring ile ilgileniyorsak ve elimizdeki verilerde ayrıştırılmış grapheme'ler bulunmuyorsa, iyi durumdayız demektir. Ama örneğin bir web sitesi yazarsak ve yabancı kullanıcıların tarayıcıları Unicode'un getirdiği serbestilerden yararlanıyorlarsa, örneğin ğ yerine g ve "birleştirme breve'sini" yan yana giriyorlarsa programımız yanlışlıklar yapabilir.
Ama kimseyi de suçlamamalıyız: hem olayın özü karmaşık, hem de tarihsel etkiler var. Örneğin İngiliz alfabesinin ASCII gibi basit bir sistemle halledilebilmesi bence kötü bir rastlantı olmuş. İnsanların kafasına "tek_kod == tek_harf" gibi yanlış bir beklenti yerleştirmiş. Hatta küçük/büyük harf dönüşümünün 32 değerinin eklenmesi ve çıkarılması ile sağlanabiliyor olması da çok iyi bir numaraymış ama o hesaba uymayan bir çok yazı sistemi var.
Öğrenmeye devam... :)
Ali
--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]