Dr. Vermes Mátyás
2006. május
A régi Clipperben és a CCC1-CCC2-ben nem volt megkülönböztetve a tetszőleges (akár bináris) adatokat tartalmazó bytesorozat és a karaktersorozatot tartalmazó string. Az ilyen típust egységesen karakternek (stringnek) neveztük, a típus kódja "C" volt. Hasonló volt a helyzet a 2.3 előtti Pythonban is.
Az idők azonban változnak, igény támadt az egyidejűleg többféle nyelven is értő programokra. Világossá vált, hogy a többnyelvűség igényeit legjobban a unicode elégíti ki, továbbá, hogy a unicode problémáit (az operációs rendszerrel való kompatibilitást illetően) legjobban az UTF-8 kódolás oldja meg. A unicode/UTF-8 kódolás ezért kezd univerzálisan elfogadottá válni, az operációs rendszerek sorra térnek át a használatára. A változást a CCC-vel is követnünk kellett. A CCC3 fő újdonsága a unicode támogatás.
Az XMLRPC esete mutatja, mennyire elkerülhetetlen a változás követése. Egy 1999-es XMLRPC leírás azt mondja, hogy a string adattípusban bámilyen adatot küldhetünk (binárisat is), csak arra kell ügyelnünk, hogy az XML formázásban szerepet játszó karakterek/byteok (<, &) megfelelően védve legyenek. A pár évvel ezelőtti XML tankönyvekben fel sem vetődik a kérdés: Miből áll az XML dokumentum?
A unicode támogatás megvalósítására két út kínálkozott. A Pythonban úgy jártak el, hogy bevezettek (mint új dimenziót) egy új típust, a unicodeot, ami minden mást érintetlenül hagyott. A kompatibilitás szempontjából ez tökéletes megoldás, azonban semmivel nem visz közelebb a régebbi programok unicodeosításához.
A CCC-ben a Jáva mintáját követve radikálisabb utat választottunk. Bevezettünk egy új típust a bytesorozatok számára. Ezt bytearraynek, bytesorozatnak, vagy bináris stringnek nevezzük, a típuskódja "X" (sajnos a B betű már foglalt a kódblokkok számára). Ez a legalapvetőbb típus, tetszőleges értékű byteokat tartalmazhat, a többi típus többsége tárolható benne. A binary string (X) átveszi a régi (C) stringek szerepét, amikor azok bináris adatot tárolnának. A bináris stringekre működnek a szokásos string kezelő függvények és operátorok: at, rat, strtran, left, right, padr, padl, substr, +, ==, $, stb.
A korábbi (C) stringek értelmezése megváltozott, a CCC3-ban unicode karaktersorozatot jelentenek. Természetesen az ismert string függvények ezekre is működnek. A két string fajtát azonban nem lehet keverni, azaz nincs feltétlen, automatikus konverzió. Érdemes tudni, hogy az egyes karakterek C szinten wchar_t típusban tárolódnak, ami a mai C fordítókban 32 bites mennyiség.
Nagyon fontos megérteni a karakter string és a binary string közötti kapcsolatot. A karakter string (unicode vagy UCS kódok sorozata) szöveget tud tárolni. Ha a szöveget binary stringbe akarom átírni, akkor előállítom a unicode karakterek UTF-8 kódját (karakterenként a karaktertől függő hosszúságú bytesorozat), ezeket konkatenálom, az eredmény egy bytesorozat, amit a szöveg UTF-8 kódolású bináris reprezentációjának nevezek. Bármely szöveg (karakter string) ezen a módon infóveszteség nélkül bináris stringre konvertálható, és a bináris reprezentációból maradék nélkül visszanyerhető. Általában a szöveg UTF-8 reprezentációja több byte, mint ahány karakter van az eredeti szövegben. Ennek oka, hogy pl. a magyar ékezetes betűk vagy a cirill betűk UTF-8 kódja két byte. Más karakterek még hosszabbak lehetnek, a létező leghosszabb UTF-8 kód hat byteos.
Ha a karakter string memóriabeli tárolását vizsgáljuk, azt tapasztaljuk, hogy sok 0 értékű byte van benne. Nyilván, ui. az ASCII kódok a 0-127 intervallumba esnek, azaz egy byteot foglalnak el, a string azonban 32-bitet használ minden karakterhez. A unicode karakter stringekre ezért nem működnek a C könyvtár hagyományos string kezelő függvényei, amik a 0 byteot a string végének tekintik. Ugyanezért nem célszerű egy unicode stringet byteonként kiírni egy fájlba, vagy egy socketba. Ezzel szemben a string UTF-8 reprezentációja rendelkezik azzal a tulajdonsággal, hogy csakis a 0 unicodenak felel meg benne 0 byte. Az UTF-8 bináris string így alkalmas arra, hogy a program ezzel a típussal adjon meg egy fájlspecifikációt az OS számára, amire a unicode string nem felelne meg.
E megfontolásokból adódik, hogy mikor melyik string fajtát érdemes/kell használni a programokban. Alapszabály, hogy a program szövegeit karakter stringben tároljuk, és ebben a formában manipuláljuk. Vannak persze esetek, amikor ettől el kell térnünk.
Megemlítendő, hogy a unicode/UTF-8 kódolás a CCC3-ban kizárólagos. Ezen azt értem, hogy nincs támogatás semmilyen más kódolásra, pl. Latin-1-re. Ezek a (hagyományos) kódolások elavultak, és rendkívül megbonyolódik az élet, ha különféle kódolásokat kell egyszerre kezelni. Mindez azt jelenti, hogy a CCC3 használata során az ember szövegfájljai szépen átkonvertálódnak UTF-8-ra. A jelen sorokat a CCC3-mal fordított (tehát unicodeos) z editorral írom, és a saját terminálomban az angol, magyar és orosz szöveget egyformán helyesen látom (és tudom gépelni), mint ahogy helyesen látszik a TEX kimentetén és a böngészőben is. Mindehhez nincs szükség bütykölt fontokra és billentyű driverekre. Vannak tehát előnyök, amik kárpótolnak a bonyodalmakért. Bízzunk benne, hogy az UTF-8 kódolás hosszabb nyugvópont lesz a gyorsan változó informatikában.
Karakter string
x1:="Kázmér füstölgő fűnyírót húz." x2:="Копирование и распространение"A fenti értékadások szövegének kötelezően UTF-8 kódolásúnak kell lennie, másképp fordítási hiba keletkezik: INVALIDENCODING. Ebből adódóan nem nélkülözhető az UTF-8/unicode környezet. A programokat UTF-8 editorral kell írni (pl. a z-vel), a régi szövegeket át kell konvertálni. A fordító maga nem konvertál, csak hibát jelez, ha rossz a kódolás. A fordító az UTF-8 kódolású szövegből előállítja a unicode karakterek sorozatát, és ez a sorozat (vagyis a C típusú string) lesz a változók új értéke. C++ szinten a unicode (UCS) karakterek wchar_t típusban tárolódnak, ami általában 32 bites.
Binary string
x:=a"öt szép szűzlány őrült írót nyúz"A fenti értékadás eredményeképpen x típusa binary string (X), tartalma pedig a szöveget UTF-8 kódolással reprezentáló byte sorozat.
Hexadecimálisan megadott binary string
crlf:=x"0d0a"A fenti értékadás után crlf egy két byte hosszú bináris string (X), ami a 0d és 0a byteokat tartalmazza. Az x"..." stringeknél minden byteot kétjegyű hexadecimális kóddal adunk meg. Fordítási hibát okoz, ha a string páratlan számú betűt, vagy nem hexa számot tartalmaz. A kis/nagybetű érdektelen.
Hexadecimálisan megadott számok
number255:=0xffA C-hez hasonlóan használhatunk hexadecimális egészszámokat.
Kettes számrendszerben megadott számok
number255:=0b11111111A számokat megadhatjuk bitenként is, azaz kettes számrendszerben.
Nem tudok most komplett referenciát adni az újdonságokról, mert nem emlékszem mindenre fejből. Ideiglenesen csak annyit akarok leírni, ami már megfelelően érzékelteti a dolgok hangulatát és logikáját. A pontos infóhoz állandóan nézni kell a forrásokat, magam is ezt teszem.
Új függvények:
Módusult függvények.
Internacionalizálásnak sok összetevője van, mi itt csak egy dologgal foglalkozunk: Hogyan lehet többnyelvű CCC programot írni, amiben a string konstansok egyszerűen cserélhetők különféle nyelvi változatokra. Egy működő példa található a $CCCDIR/tutor/nlstext directoryban, ezt a példát magyarázom el részletesen az alábbiakban.
Az nlstext.prg program:
static x:=@"Some like hot" function main() nls_load_translation("nlstext") fun() ? static function fun() static y:=@'Gentlemen prefer blondes' local z:=@"Star war" ? x ? y ? z ? @"Matrix" ?Először is azokat a stringeket, amiket a program különböző nyelvű verzióiban fordításban akarunk látni, meg kell jelölnünk. Erre szolgál a speciális @"..." szintaktika. A programfordítás idejére beállítjuk az alábbi környezeti változót:
export CCC_NLSTEXT_TAB=$(pwd)/nlstext.tranEnnek hatására a ppo2cpp fordító kigyűjti nekünk a kukaccal megjelölt stringeket egy szövegfájlba, esetünkben nlstext.tran-ba:
"Some like hot"<<"" from ./nlstext.prg (21) "Gentlemen prefer blondes"<<"" from ./nlstext.prg (33) "Star war"<<"" from ./nlstext.prg (34) "Matrix"<<"" from ./nlstext.prg (39)Itt soronként egy stringet találunk. A sor a lefordítandó stringgel kezdődik, utána jön egy << jel, majd egy üres idézet, ahová a fordítást kell majd beírni. Az eddigiek azt jelölik, hogy a bal oldali stringet helyettesíteni fogja a jobb oldalra írt fordítás. A sor végén fel van tüntetve, hogy az adott string melyik forrásfájl melyik sorából származik. Természetesen, ha a project sok forrásfájlból áll, akkor az egyes fájlokból jövő járulék halmozódik, ezért egy nagyobb program esetén ezres nagyságrendű sor lehet az eredmény.
Minden nyelvhez készítünk egy-egy directoryt, esetünkben
translation/hu translation/ruezekbe átmásoljuk az nlstext.tran egy-egy példányát, ezeken fognak dolgozni a fordítók. A fordító munkájának eredménye egy ilyen fájl:
"Some like hot"<<"Несколько мужчин любят горячо" from ./proba.prg (21) "Gentlemen prefer blondes"<<"Господа любят лучше блондинок" from ./proba.prg (33) "Star war"<<"Война эвёэд" from ./proba.prg (34)Ebből a fájlból a tran2cpp utility C++ forrást generál, amit lefordítunk, és dinamikus könyvtárat linkelünk belőle. Elvégezzük ugyanezeket a műveleteket a magyar változatra is. A dinamikus könyvtárak neve:
translation/libnlstext.hu.so translation/libnlstext.ru.soTermészetesen ugyanez megy Windowson is, csak ott dll-eket kapunk.
Namost, ha az nlstext.exe programot egy ilyen scripttel indítjuk:
#!/bin/bash export CCC_LANG=ru export LD_LIBRARY_PATH=./translation:$LD_LIBRARY_PATH nlstext.exeakkor a program elején található
nls_load_translation("nlstext")függvényhívás (amiről eddig nem szóltunk) a CCC_LANG változó értékéből és a paraméterként kapott "nlstext" szövegből összerak egy könyvtárnevet, és a könyvtárat megpróbálja betölteni. Ha ez a betöltés sikeres, akkor a program a @"..." stringek helyett azok fordításait fogja megjeleníteni. Ha a fordításkönyvtár dinamikus betöltése nem sikeres, vagy a könyvtár nem tartalmaz fordítást egyik vagy másik stringre, attól még működni fog a program, csak ekkor a fordítással nem rendelkező stringek eredeti szövege jelenik meg.
A megjelenítő modulok átfogó profiltisztításon estek át. A CCC3-ból kimaradtak az uiw, uif, uid könyvtárak. Ha egy alkalmazás ezekre épül, akkor azt továbbra is a CCC2-vel kell fordítani (aminek a karbantartása folytatódik), vagy alkalmazássszinten kell megoldani a könyvtárak portolását (a CCC2-beli állapotukban ezek nem támogatják az UTF-8-at). A CCC3 háromféle megjelenítőkönyvtárat tartalmaz:
Egyszerűsítések történtek az uic könyvtáron belül is. A CCC2-ben (UNIX-on) a karakteres megjelenítés történhetett X-szel, az ncurses könyvtárral, illetve távoli terminálon. A CCC3 csak az utóbbit (a távoli terminált) tartja meg. Ha egy alkalmazást a lokális munkaállomáson futtatunk, és ugyanitt akarjuk megjeleníteni, akkor is a terminált kell használnunk. A terminál konfigurálásával a következő fejezet foglalkozik. A (távoli) terminál előnye, hogy az uic könyvtárnak nincs külön UNIX-os és windowsos változata, ui. az uic feladatai a terminállal való kommunikációnál véget érnek. A tényleges megjelenítéshez persze kell egy terminál program UNIX-ra és egy másik Windowsra, ezek foglalják magukba a megjelenítés platform specifikus részleteit.
A szerverprogram és a terminál többféle módon kapcsolódhat. Sorravesszük a lehetőségeket:
export CCCTERM_CONNECT="a_terminál_teljes_fájlspecifikációja"Ez a legegyszerűbb módja, hogy egy gépen CCC3 környezetben dolgozzunk. A környezeti változó tudatja a programokkal, hogy el kell indítaniuk maguk számára a terminált, ezt meg is teszik, a program és a terminál automatikusan konnektálnak.
Elindítjuk az xstart-ot (CCC listener) a szerveren, az xstartnak az alábbi paraméterfájlt adjuk meg:
<xstart> <item> <name>Test Program</name> <port>55000</port> <env>CCCTERM_CONNECT=SOCKET:$(SOCKET)</env> <command>proba1.exe</command> </item> </xstart>Ha most elindítjuk a terminált egy másik gépen
terminal.exe host 55000ahol host az xstartot futtató gép, akkor a lokálisan futó terminálban megjelenik a host-on automatikusan elinduló proba1.exe program képernyője. Figyeljük meg, hogy a CCCTERM_CONNECT környezeti változó (most egy másik szintaktikával) a listenertől örökölt socket leíróját tudatja a szerver programmal.
Elindítjuk az xstart-ot lokális munkaállomáson az alábbi paraméterfájllal:
<xstart> <item> <name>terminal</name> <port>55000</port> <env>CCCTERM_SIZE=80x43</env> <command>terminal.exe --socket $(SOCKET)</command> </item> </xstart>
A (távoli) szerveren beállítjuk:
export CCCTERM_CONNECT=host:55000Ahol host a munkaállomásunk neve (vagy IP címe), ahogy azt a távoli szerver ismeri. Ha ezután a szerveren elindítunk egy tetszőleges karakteres CCC3 programot, akkor a lokális munkaállomáson automatikusan elindul a terminál, és abban megjelenik a távoli gépen futó program képernyője. Figyeljük meg, hogy a CCCTERM_CONNECT környezeti változó (most egy harmadik szintaktikával) azt tudatja a szerver programmal, hogy hova kell konnektálni a megjelenítés érdekében.
Természetesen lehetséges, hogy az előző esetek akármelyikében, a szerver és a munkaállomás ugyanaz a gép legyen.
A szerver program (uic könyvtár) és a terminál alapvetően kétféle módon juthatnak hozzá a kommunikációs sockethez, konnektálnak, vagy öröklik a socketet:
Ha a szerveren beállítjuk:
export CCCTERM_CONNECT=host:portakkor a program a host:port címre próbál konnektálni, ahol egy listenernek kell várnia a konnektálásokat, és indítania a terminált. Ha a terminált így indítjuk:
terminal.exe host portakkor a terminál a host:port címre próbál konnektálni, ahol egy listenernek kell várnia a konnektálásokat, és indítania a szerver alkalmazásokat.
A másik oldalon, a listener úgy indítja a szerver alkalmazást, hogy beállítja számára a
export CCCTERM_CONNECT=SOCKET:sckkörnyezeti változót, amiből a szerver program (uic könyvtár) értesül az örökölt socket leírójáról (sck). Ha a listener így indítja a terminált,
terminal.exe --socket sckakkor a terminál tudja, hogy sck-ban az örökölt socket leíróját kapta meg.
Speciálisan kezelhető a legegyszerűbb eset, amikor az
export CCCTERM_CONNECT="a_terminál_teljes_fájlspecifikációja"beállítás hatására a szerver program automatikusan indítja maga számára a terminált.
A terminál egy sor további környezeti változóval paraméterezhető: