Újdonságok a CCC3-ban

Dr. Vermes Mátyás

2006. május

1.  Unicode támogatás
2.  String (és egyéb) literálok
3.  Bővített függvény API
4.  Internacionalizálás
5.  Megjelenítő könyvtárak
6.  Karakteres terminál
    6.1.  Konnektálás
        6.1.1.  Lokális szerver és terminál
        6.1.2.  Listener a szerveren
        6.1.3.  Listener a munkaállomáson
        6.1.4.  Összegzés
    6.2.  A terminál paraméterei

1.  Unicode támogatás

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 mai XML szabvány szerint karakterek sorozatából. Az XMLRPC string tehát nem tartalmazhat bináris adatot, mert akkor elbukik az XML elemzésen. Hogy mik a karakterek, az sem triviális, pl. a 0x00-0x20 intervallumban csak a TAB, CR, LF számít karakternek, és máshol is vannak érvénytelen (nem karakter) kódok, amikre a mai XML elemzők kivételt dobnak.

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.

A koncepció, hogy az alkalmazási programokban minél kevesebbet kelljen váltogatni a bináris és karakter reprezentáció között, ehelyett a CCC könyvtár függvényei alkalmas helyen automatikusan elvégzik a konverziót. A memoread-et pl. általában arra használjuk, hogy egy szövegfájlt egy mozdulattal beemeljünk egy karakterváltozóba. Ezért a memoread automatikusan karakter stringre konvertálja, amit olvas. Néha azonban más kell, pl. amikor egy png képfájlt olvasunk be, ezért a memoread kiegészült egy opcionális paraméterrel, amivel kikapcsolható ez a konverzió. Ilyenkor a memoread eredménye nem karakter, hanem binary string. Az fopen a filénevet UTF-8-ra konvertálva adja lejjebb a C szintnek. Jelenleg még nincs kikristályosodva, hogy hova célszerű ezeket az automatikus konverziókat tenni. Azokon a helyeken, ahol a stringtípusok találkoznak, az alkalmazásnak mindenképpen explicite kell konvertálnia, ezért a programok elkerülhetetlenül bonyolultabbak lesznek, mint a CCC2-ben voltak.

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.

2.  String (és egyéb) literálok

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:=0xff

A C-hez hasonlóan használhatunk hexadecimális egészszámokat.

Kettes számrendszerben megadott számok  

    number255:=0b11111111

A számokat megadhatjuk bitenként is, azaz kettes számrendszerben.

3.  Bővített függvény API

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:

bin(code)
A chr(code) bináris párja. Egy 0-255 közé eső kódból egy byte hosszú bináris stringet készít.
arr2bin(a)
A korábbi _arr2chr-t pótolja. Most nyilván a karakter stringeket is sorosítani kell, az eredmény egy bináris string (bytesorozat).
bin2arr(x)
arr2bin(a) inverze.
str2bin(c)
Előállítja a c karakter string UTF-8 kódolású bináris reprezentációját. Ebből információveszteség nélkül visszanyerhető az eredeti string. A bináris reprezentáció sok helyen helyettesítheti is c-t.
bin2str(x)
str2bin(c) inverze. Tudni kell azonban, ha x nem érvényes UTF-8 kódolású szöveget tartalmaz, akkor információ vész el, pl. egy png képfájl elromlik.
split(v,sep)
Helyettesíti a megszűnt wordlist-et. Karakteres és bináris stringekre is működik.

Módusult függvények.

chr(code)
Egy 32 bites UCS kódból egy egy karakter hosszú stringet készít.
asc(v)
Ha v karakter string, akkor az első karakter UCS kódját adja. Ha v bináris string, akkor az első byte értékét adja.
left(v,n)
Ha v karakter string, akkor v első n karakteréből álló részstringet adja. Ha v bináris string, akkor v első n bytejából álló bináris részstringet adja. Utóbbi esetben, ha v egy szöveg UTF-8 kódolású bináris reprezentációja, akkor ez a tulajdonság elromolhat, amennyiben a left elvág egy több byteos UTF-8 kódot.
len(v)
Ha v karakter string, akkor a v-ben levő karakterek számát adja. Ha v bináris string, akkor a v-ben levő byteok számát adja.
replicate(v,n)
A v változó karakter és bináris string is lehet, az eredmény ennek függvényében C vagy X típusú. A rekordbuffereket régen a space függvénnyel hoztuk létre. Mivel ennek az eredménye C típus, ez most általában nem jó, helyette ilyesmit írunk: replicate(x"20",n).
fread(fd,@buf,n)
Az fread nem karaktereket, hanem byteokat olvas, ezért buf-ot X típusúra kell inicializálni.
fwrite(fd,buf,n)
Az fwrite nem karaktereket, hanem byteokat ír, ezért, ha C típusú buf-ot adunk meg neki, azt automatikusan átkonvertálja X típusra str2bin-nel.
convertfspec2nativeformat(f)
Az eredményét mindig átkonvertálja binárisra, ui. az operációs rendszernek UTF-8 kódolású fájlspecifikációkat lehet megadni.
hashcode(v)
Karakteres és bináris stringekre is működik.
isalpha(v)
Karakteres és bináris stringekre is működik. C szinten az iswalpha, illetve az isalpha hívódik meg. Karakter string esetén az ékezetes és cirill betűkre is jó eredményt ad.
qout(c,...)
A karakter stringek kinyomtatás előtt automatikusan átkonvertálódnak UTF-8-ra (vagyis binárisra), ui. az operációs rendszerek ezt értik.
savescreen()
A képernyő bináris stringként mentődik, egy screen cella a korábbiaktól eltérően most 4 byte, mert UCS kódokat kell tárolni. A függvénycsalád összes tagja ehhez alkalmazkodik.
upper(v)
Karakteres és bináris stringekre is működik. C szinten a towupper, illetve a toupper hívódik meg. Karakter string esetén az ékezetes és cirill betűkre is jó eredményt ad.
val(x)
Karakteres és bináris stringekre is működik.
valtype(v)
Bináris stringre "X"-et ad.
like()
Karakteres és bináris stringekre is működik.
memoread(fspec [,binopt])
Ha a binopt empty, akkor bin2str-rel karakterre konvertálja a beolvasott fájl tartalmát. Ez csak akkor jó, ha a fájl UTF-8 kódolású szöveget tartalmaz. Ha egy png képfájlt akarunk beolvasni, akkor azt binopt:=.t.-vel tesszük, az eredmény ilyenkor egy bináris string.
memowrit(fspec,v)
Ha v egy karakteres string, akkor azt kiírás előtt átkonvertálja binárisra.
inkey()
Az inkey kódok megváltoztak, lásd az inkey.ch-t.

4.  Internacionalizálás

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.tran

Ennek 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/ru

ezekbe á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.so

Termé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.exe

akkor 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.

5.  Megjelenítő könyvtárak

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:

ccc3_uic
Ez a hagyományos karakteres megjelenítés, ami messzemenően kompatibilis az eredeti Clipperrel, ám a CCC3-beli megvalósítás támogatja a unicodeot.
ccc3_gtk
Interfész a GTK-hoz. A GTK egy platformfüggetlen grafikus könyvtár, amihez számos nyelv tartalmaz csatolót. A GTK mindig is UTF-8 kódolással dolgozott, most ehhez jól illeszkedik a CCC3. Lásd: CCC-GTK csatoló.
ccc3_jt
Saját találmány a Jáva megjelenítő modul (Jáva terminál). A Jáva szintén a kezdetektől támogatta a unicodeot, most ezt a tulajdonságát kihasználja a CCC3. Lásd: Jáva Terminál.

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.

6.  Karakteres terminál

6.1.  Konnektálás

A szerverprogram és a terminál többféle módon kapcsolódhat. Sorravesszük a lehetőségeket:

6.1.1.  Lokális szerver és terminál

    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.

6.1.2.  Listener a szerveren

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 55000

ahol 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.

6.1.3.  Listener a munkaállomáson

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:55000

Ahol 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.

6.1.4.  Összegzés

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:port

akkor 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 port

akkor 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:sck

kö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 sck

akkor 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.

6.2.  A terminál paraméterei

A terminál egy sor további környezeti változóval paraméterezhető:

CCCTERM_SIZE
CCCTERM_REDIR_CONSOLE
CCCTERM_REDIR_PRINTER
CCCTERM_REDIR_ALTERNATE
CCCTERM_REDIR_EXTRA
CCCTERM_REDIR_ERROR
CCCTERM_CAPTURE_PRN
CCCTERM_CAPTURE_LPT1
CCCTERM_CAPTURE_LPT2
CCCTERM_CAPTURE_LPT3
CCCTERM_FONTSPEC
CCCTERM_FONTFACE
CCCTERM_FONTSIZE


Learn Hungarian in Budapest in Ulysses language school. Group and private courses on affordable prices.