.    

POZOR! Tento článek byl naposledy aktualizován před více než dvěma lety!

Je možné, že následující­ stránka obsahuje odkazy, které dnes již nejsou funkční, nebo že některé informace uvedené v tomto článku se v průběhu času ukázaly jako prokazatelně chybné. Pokud jakoukoliv podobnou závadu zjistíte, tak neváhejte napsat co nejpřesnější popis závady do veřejného komentáře pod článkem: redakce TečkyCZ nové komentáře neustále sleduje, a to i pod těmi nejstaršími články. V celé řadě případů lze chyby snadno opravit - např. se stává, že video na YouTube bylo smazáno a znovu nahráno pod jiným id. V jiných případech někdo zase zakáže embedovaní videa, která přitom existuje ve více kopiích, nebo se z webu ztratí stránka umístěná na negarantovaném freehostingu, zatímco původní autor stránek si mezitím zaregistruje vlastní doménu, atd.

Děkujeme všem, kteří pomáhají opravovat chyby ve starších webových stránkách a udržují tak Internet naživu - redakce TečkyCZ.


Učebnice jazyka C<<1 - 3.část

xChaos 31. května 2003 [16515 znaků] [editováno 17. března 2006] [HowKnow]
★★ [ + ] 2 [2x] [ - ]
Zobrazení 21295 ← Facebook 4 Twitter 20 Google 193
Komentářů 3

Zájemci o programování v jazyce C<<1 se mohou seznámit s verzí 0.3 a přeložit si pod Linuxem celou řadu ukázkových programů
Z učebnice C<<1 se nakonec vyvinula spíš zuřivá obhajoba jiných než objektových a skriptovacích přístupů k programování. Stručně připomínám z diskuze na začátku prvního dílu učebnice:

  • Proč se neučím C++ ? Protože je vhodné spíš pro lidi schopné namemorovat obrovské množství předdefinovaných tříd, případně "templates". Možnost předefinovat význam jakéhokoliv operatoru je pěkná, ale bohužel, síla C++ je pro běžného smrtelníka příliš velká. Já potřebuji jednoduchý nástroj, který bude vypadat jako kladivo, a kterým půjdou zatloukat hřebíky.

  • Proč se neučím Javu ? Protože věřím, že kombinace open source a binárního kódu kompilovaného z open source zdrojového kódu je mocnější nástroj a perspektivnější cesta (pro mě - ne pro firmu Sun :-), a navíc mě nikdo nepřesvědčil o přenositelnosti Javy mezi platformami. Ještě nikdo mi nepředvedl Java applety, které by běhaly beze změny ve webovém browseru i mobilním telefonu. Možná existují - ale já o nich zatím neslyšel. Java je zajímavá vize, která se ale nerealizovala.

  • Proč mi nestačí různé Perly, PHP, SQL a jiné populární nástroje pro tvorbu reálných aplikací ? Protože jsou prostě chvíle, kdy potřebujete aby váš server neměl load average 0.99. Měl jsem možnost vidět, co udělala kombinace PostgresQL a FastCGI v Perlu s dvouprocesorovým webhostingovým serverem - a děkuji, nechci. Jsou prostě věci, které by měly být optimalizované na úrovni překladu a navíc ještě přeložené do strojového kódu.

  • Nemá smysl psát zcela nový programovací jazyk. Původně jsem si představoval jazyk, jehož interpeter by byl kombinovaný s prekompilerem, generujícím ANSI C zdrojový kód. I když paralelní možnost interpretace a překladu je jistě optimální možností, pochybuji, že by dnes byl kdokoliv ochoten se učit zcela nový jazyk. Dokonce i Java nebo PHP syntakticky vycházejí z klasického C - které je dnes jednoznačně rozšířenější, než třeba znalost Basicu či Pascali...

  • Ukázalo se, že nejlepším místem pro tvorbu nového jazyka je makroprocesor vestavěný v každém moderním C kompileru. Pokud to někomu ještě není jasné, připomínám co takový přístup přináší za výhody: nemusíte program linkovat s externími knihovnami (jako např. v případě glib) a překompilují se pouze ty makra, která ve svém programu použijete. Překlad je velice rychlý. Jedinou možnou nevýhodou je redundance kódu, v některých případech - té ale lze zabránit vhodným strukturováním svého vlastního rozsáhlejšího projektu do modulů. V podstatě žádné mnou definované makro jazyka C<<1 nemůže po kompilaci zabrat více, než několik desítek bajtů, a tak pokud objektoví programátoři mohou zanedbat overhead svého přístupu s poukazem na rostoucí výpočetní výkon i kapacity paměti, já mohu prohlásit za zanedbatelný overhead makroprogramování - a to i v případě, že se moje makra použijí hodně špatně.

    Tak, tolik "stručný" úvod, víceméně zopakování argumentů z první kapitoly - a nyní přejděme ke stručnému líčení postupu vývoje jazyka C<<1.

    Header file cll1.h se nyní nachází ve verzi 0.3.

    Pro přeložení příkladů na které se budu odkazovat dále v textu je třeba header file stáhnout a uložit do pracovního adresáře spolu s příklady. Příklady jsou laděné pomocí v gcc 3.2.2 a libc 2.3.1 pod operačním systémem Linux, ale podle mě je jazyk kompatibilní s definicí ANSI C a měl by běhat na libovolném POSIXovém systému - nová verze 0.3 už předpokládá ryze POSIXová volání systému jako fork() nebo dup2(), ale na druhou stranu, výhodou je, že pokud ve vašem programu příslušná makra nepoužijete, můžete některé programy překompilovat třeba i pod DOSem (Podobným způsobem budou do C<<1 zahrnuty i vazby na složitější knihovny, například pro spolupráci s SQL (MySQL) nebo GUI (Gtk), i když pravděpodobně půjde už o samostatné oddělené header files).

    Vyslyšel jsem kritiku některých čtenářů, a začal kultivovat použitý namespace. Tak především ze spojových seznamů zmizela povinná položka next. Definice spojových seznamů nyní vypadá asi takhle:

    struct Line
    {
     int n;
     list(Line);
    } *line;
    

    (poznámka: spojový seznam integerů uvedený v tomto případě má obrovskou režii... ale jsou přesto situace, kdy se může hodit. Jak uvidíte v dalších příkladech, C<<1 se orientuje na programátory, kteří už zplodili příliš mnoho programů operujících se soubory o maximální délce řádku 256 bajtů,alokovat statická pole pro maximálně šestnáct záznamů, apod. a teď chtějí programovat trochu profesionálněji...)

    Spojové seznamy jsou jinak poměrně stabilní součást C<<1, která prošla zatěžkávací zkouškou. Otázkou je, jestli by se místo every() nemělo psát třeba foreach() - ale v budoucnu lze tyto detaily určitě snadno doladit, až vznikne otevřená diskuze uživatelů C<<1, ne jenom jeho nechápajících kritiků, jako dosud :-) Novinkou v oboru seznamů je makro

    #define find(A,B,C) search(A,B,C) break; if(A)
    

    jehož význam je zřejmý - na rozdíl od search není iterací, ale podmínkou (to dělá ten break a středník :-), a lze za něj tím pádem psát else - ovšem opet pozor na středník, pokud před else není žádný příkaz

    find(zaznam,zaznamy,zaznam->hodnota==COSI); else puts("COSI nenalezeno!");
    

    Rozšířena byla nabídka kritérií pro třídění seznamů. Tím se začalo C<<1 distancovat od hsitorického DOSovského Borland C, protože GNU libc místo strncmp() používá strcasecmp(). Nyní lze tříděni podle abecedy provozovat buď ignorecase, nebo ASCII - přičemž pro korektní české třídění by myslím v libc podpora být měla. Každopádně kritérium sort_by nyní třídí bez ohledu na velká a malá písmena, k dispozici je nově kritérium ascii_by a desc_ascii_by.

    Do C<<1 nově přibyla I/O sekce, zatím zaměřená pouze na soubory (rozšíření na TCP/IP sockety se dá očekávat ve verzi 0.4. Zajímavé, ale po zralé úvaze nepříliš elegantní možnost nabízejí makra

    #define fparse(S,L,F) for(fgets(S,L,F);*S && !feof(F);fgets(S,L,F))
    #define input(S,L) fparse(S,L,stdin) 
    

    Tato makra měla být původně kostrou I/O operací v C<<1, ale byla shledána málo převratnými. Makro input() se původně tvářilo hodně slibně, jak je vidět např. v ukázce shell.c, ale základním problémem zůstáva potřeba alokace řetězce fixní délky. Poté, co jsme pro práci se stringy zavrhli funkce typu strcpy() a rozhodli se s řetězci pracovat vlastním stylem, se jako daleko vhodnější jeví konstrukce

    parse(radek,"soubor.txt")
    {
     //string radek je nove alokovan pro kazdy radek textoveho soboru
     nejak_zpracuj(radek);
    (
    fail
    { 
     perror(argv[1]); 
     exit(-1);
    }
    done;
    

    Zájemci o deklaraci maker parse, fail a done se mohou na jejich ne zcela triviální implementaci podívat dovnitř header file cll1.h. Podotýkám, že vše je definované tak, že makro fail a následnou sekvenci příkazů lze vynechat; nelze ovšem vynechat makro done. Použití parse je předvedené například v příkladu split.c

    Přínos makra parse() spočívá v tom, že alokuje pro každý řádek přesně tolik paměti, kolik má řádek znaků. Není zde tedy žádné omezení kombinované s bohorovným plýtváním. Předpokládá se, že alokované řádky se buď rovnou zpracovávají (a případně dealokují - free(radek). pokud je třeba šetřit), a nebo ukládají do spojového seznamu. Vzhledem k tomu, že velikost alokované paměti pro jednotlivé řádky neznáme, je při použití makra parse() potřeba důsledně hlídat, abychom s takto vzniklými stringy nepracovali jako s cílovými pointery pro strcmp() a podobně se chovající funkce.

    Bohužel, makro parse() není jednoduše přenositelné pro stdin. Ve verzi 0.4 se tuto funkčnost pokusím do C<<1 vnést pomocí systémové služby select(), výsledek zatím nelze předvídat. Pojďmme nyní dál.

    Od makra parse() jsou odvozená makra load() a save(). Použití makra load(), stejně jako použití makra textfile() pro jednoduchou deklaraci spojových seznamů řetězců je předvedeno v příkladu text.c.

    Následuje sekce sekvencí pro přesměrování standartního vstupu a výstupu. Tato sekce využívá hardcore volání fork() a dup2(), a je snahou zahojit jizvy, které na mé psychice zanechal pokus o studium na MFF UK :-) Makra umožňují využít některá fakta, která jsem se tam o Unixu dozvěděl, aniž by se z toho programátor zbláznil :-) Vužití maker shell() a paste() budu demonstrovat rovnou na funkčním programu:

    #include "cll1.h"
    
    int main(void)
    {
     paste("Subject: C<<1 sample\n\nZdrojak programu ti prijde dalsim \
            mailem... mrkni se na to\n");
     system("/usr/sbin/sendmail cxl@volny.cz");
     
     shell("cat cll1test.c");
     system("/usr/sbin/sendmail cxl@volny.cz");
    }
    

    Co presne program dela, se da pri dnesni povedomi o linuxu/unixu asi nejlepe vysvetlit tim, ze si ukazame, jak by se to same napsalo v unixovem shellu:

    #!/bin/bash
    echo "Subject: C<<1 sample\n\nZdrojak programu ti prijde \
          dalsim mailem... mrkni se na to\n"|/usr/sbin/sendmail cxl@volny.cz
    cat cll1test.c|/usr/sbin/sendmail cxl@volny.cz
    

    Stručně řečeno: makro paste() pošle na standartní vstup konkrétní řetězec - což má význam pokud ze svého programu voláme další programy, které ten vstup očekávají (tak jednoduché, že bychom dali fputs(cosi,stdin) to opravdu není... ale nezkoušel jsem to... :-). Makro shell() pak na standartní vstup přesměruje standartní výstup programu, který dostane jako svůj parametr. Do C<<1 se díky dostávají prvky skriptování, a původní duch unixových systémů: jednotlivé spustitelné programy jsou opravdovými objekty, které si mezi sebou mohou posílat "zprávy" v původním duchu objektově orientovaných systémů.

    Další příklady využití shell() a paste() najdete v příkladu shell.c.

    Dostáváme se k nejobtížnější sekci, kterou je práce s řetězci. Ta není obecně v ANSI C vůbec snadná, protože se předpokládá implementace NULL terminated řetězců, které neobsahují žádnou informaci o své alokované délce. C<<1 přišlo s jednou zásadní tezí - a sice, že řetězce nesmějí být cílem kopírování obsahu paměti nebo standartního vstupu. Jak jsem už psal v prním díle, funkce jako gets() nebo strcpy() jsou z definitivní platností zakázané - a bohužel také na často používané strcat() nebo sprintf() bude potřeba zapomenout... Pokud máme pointer na textový řetězec, máme povolenu jednu základní operaci: nahradit JEDEN znak, na který pointer ukazuje, a to ještě pouze v případě, že tento znak není 0.

    Na první pohled to vypadá, že náš přístup musí vést k nesmírným memory leakům. Skutečně, do budoucna bude třeba zkombinovat tento přístup s kontextově orientovanou alokací paměti, nikoliv nepodobnou přístupu, který razí glib. Totiž ještě jinak - okolo verze 0.5 se dá očekávat, že makra string() a create() nebudou používat natvrdo malloc(), ale že bude možné příkaz pro alokaci paměti předefinovat libovolným vlastním konstruktorem.

    Prozatím se spokojíme s tvrzením, že unixový program má být malý, stručný a jednoduchý, má dělat jednu věc a potom hned skončit. Během té doby snad nestihne vyplýtvat příliš mnoho paměti. No .. ke tvorbě monstrózních aplikací a GUI se asi dostaneme až později :)

    Takže si to shrneme: textové stringy v C<<1 nemají nikdy známou délku, tudíž by si všechny funkce, které s nimi pracují měly ověřovat, že nezapisují za jejich konec. Jakékoliv deklarace délek řetězců jsou pouze záležitostí zdrojového kódu, runtime o deklaracích nic neví. Stringy vznikají ostatně jen několika málo způsoby: jako pointery na textové konstanty, deklarované přímo uvnitř programu, jako pointery na parametry příkazové řádky, často se alokují z jednotlivých řádek textového souboru makrem parse(), občas vznikají duplikací existujících stringů makrem duplicate() (resp. dalšímy makry na kterými zatím medituji a které budou zahrnuty do verze 0,4 jazyka C<<1 :-) ukazuje se hlavně naléhavá potřeba korektní náhrady za strcat() a sprintf()...) no a když už to opravdu nemůže jinak být, tak vznkají pomocí makra string() - jak se ale ukazuje, tak pokud programujete správným zůsobem (a nikoliv podle manuálu k Borland C++ 3.1, na kterém jsem ale bohužel vyrůstal...), tak by vám měly tyto způsoby vzniku textového stringu stačit.

    Když jsme si vyjmenovali, co všecho se v C<<1 se striny dělat nemá, pojďme se podívat na některé vymožnosti, které máme naopak k dispozici. Jde zatím o tato makra:

    #define eq(A,B) !strcmp(A,B)
    #define strcmpi(A,B) strcasecmp(A,B)
    #define string(S,L) (S=(char *)malloc(L),*S=0)
    #define duplicate(A,B) if(A) { B=string(strlen(A)); strcpy(B,A); }
    #define suffix(A,B,C) (((A=strrchr(B,C))&&!(*(A++)=0))||(A=B))
    #define prefix(A,B,C) ((A=B)&&((B=strchr(B,C))&&!(*(B++)=0)||(B=A)))
    #define gotoalpha(A) if(A)while(*A && !isalpha(*A))A++
    #define goto_alpha(A) if(A)while(*A && !isalpha(*A) && *A!='_')A++
    #define gotoalnum(A) if(A)while(*A && !isalnum(*A))A++
    #define goto_alnum(A) if(A)while(*A && !isalnum(*A) && *A!='_')A++
    #define skipalpha(A) if(A)while(*A && isalpha(*A))A++
    #define skip_alpha(A) if(A)while(*A && (isalpha(*A) || *A=='_'))A++
    #define skipalnum(A) if(A)while(*A && isalnum(*A))A++
    #define skip_alnum(A) if(A)while(*A && (isalnum(*A) || *A=='_'))A++
    #define gotochr(A,C) if(A)while(*A && *A!=C)A++
    #define split(A,B,C) for(prefix(A,B,C);A;(A!=B)&&prefix(A,B,C)||(A=NULL))
    
    

    Některá znáte už z předchozích dílů učebnice, strncmpi je pouze berlička pro beznadějné oběti Borland C. Jestliže duplicate() bylo záludné tím, že alokuje novou paměť aniž by řešilo dealokaci té staré (ke korektní garbage colleciton se dostaneme asi tak někdy okolo verze 1.0...), pak nová makra jsou ještě radikálnější v tom, že se k pointerům chovají destruktivně: je tedu vhodné je volat na kopie pointerů, pokud potřebujeme s původním pointerem ještě někdy něco užitečného dělat.

    Makra prefix() a suffix() rozříznou řetězec podle konkrétního znaku C - například dvojtečka, tabulátor, apod. Rozříznou ho tak důkladně, že původní pointer B po operaci ukazuje na na část co zůstala "vcelku" (zleva nebo zprava), a nový pointer A ukazuje zásadně na "odřízlé" slovo (zleva nebo zprava). V případě nenalezení znaku C se pointer A pouze odkazuje na B. Makro split() je pak podle mě velice zdařilou iterací, která rozřeže řetězec B na jednotlivá slova A oddělená znakem C, a pro každý možný obsah slova A vykoná následucí (složený) příkaz. Příkladem použití makra split je program split.c, který načte do paměti obsah souboru /etc/passwd a vypíše uživatele setříděné jednak podle abecedy, jednak podle uid - což je mimochodem transkace, kterou už třeba v shellu nejde jednoduše napsat.

    Makra goto* a skip* jsou jenom slabomyslným rozvedením myšlenky posouvání pointeru - jsou to jednoduchoučké konečné automaty, kterými lze šplhat po textovém řetězci. Jejich názvy jsou odvozené od názvů funkcí v headeru ctype.h.

    Na závěr reaguji na výzvu v bouřlivé diskuzi pod prvním dílem učebnice C<<1, a uvádím implementaci programu pro zobrazení statistiky výskytu platných identifikátorů jazyka C v daném textovém souboru - po pravdě řečeno, oproti referenční implementaci nerozlišuji čísla, a naopak ignoruji vnitřek řetězců v úvozovkách, složitost vyjde asi najstejno. Program idmap.c vypíše statistiku výskytu identifikátorů setříděnou buď podle četnosti nebo podle abecedy; můžete srovnat s implementaci v C++ za použití toolkitu NTL, k jejíž reimplementaci mě cxl vyzval. Posuďte sami, která kód vám přijde čistší nebo srozumitelnější... :-)

    Odkazy:

  • 1. díl učebnice C<<1
  • 2. díl učebnice C<<1
  • cll1.h - vlastní implementace C<<1 formou header file
  • idmap.c - ukázka parse, find, sort...
  • text.c - ukázka load, every...
  • lists.c - ukázka insert, sort, search...
  • split.c - ukázka parse, split, sort...
  • shell.c - ukázka shell, paste, input...

  • Sloupcová sazba: pokud je okno prohlížeče dostatečně velké (na monitoru s dostatečným rozlišením), zobrazí se článek ve více sloupcích (w3.org). Testováno v browserech Firefox, Opera a Chrome. Není implementováno v Internet Exploreru. Tato feature může způsobovat problémy ve starších verzích prohlížečů s jádrem Webkit (Google Chrome, Safari, Konqueror). Pokud nevidíte článek celý, zkuste zmenšit okno prohlížeče nebo použít verzi pro tisk. [zpět na začátek sloupcové sazby]
    Pokud se vám článek líbil, zkuste autora podpořit [zobrazit možnosti]
    Sdílet v síti [Identi.ca - musíte být předem přihlášeni] [Twitter] [Facebook] [Jagg.cz]
    Formátovat pro tisk [bez komentářů] [s komentáři]
    Krátká forma URL (adresy) [http://teckacz.cz/135]
    Všechny články [od autora xChaos] [v rubrice HowKnow] [nejnovější]

    Hodnocení článku čtenáři [ + ] 2 [2x] [ - ]
    Tip: Pro moderaci článků (kladné nebo záporné hodnocení) je nutné použít browser, který podporuje javascript a cookies.
    Komentáře čtenářů [napsat vlastní]
    Skrýt hodnocené nebo méně


    [] Toom (anonym) 2. srpna 2003 ← komentářů 2 1 [1x]
    [ + ] 0 [0x] [ - ] ← pro ohodnocení komentáře se není nutné nikde registrovat
    → [/-/605] ← na komentář můžete odpovědět nebo ho sdílet
    <p>Porad v tom makru duplicate (nebo string, podle toho jak se to vezme) mas problem s nedostatecnou alokaci - chybi 1 byte pro koncovy znak \0.
    </p><p>
    JInak co se tyce te vlastni ideje C<<1 jazyka - je to zajimave uz jen z toho duvodu, ze minimalne z tech ukazek se zda, ze to uplne nepouzitelne neni. Jen si dej pozor na to, ze se to stejne jednoho dne rozroste natolik, ze clovek, ktery to uvidi poprve si klidne bude moct rict, ze je to pro nej moc slozite (presne v duchu tveho odporu k rozsahlym knihovnam).
    </p><p>
    Stejne si myslim, ze je velka skoda, ze se tady na skolach neuci OCaml misto napr. LISPu. To je totiz funkcionalni i imperativni jazyk, ktery nema tu zavorkovou LISPovskou syntaxi. A co je hlavni ma interpreter i kompilaci do binarky i kompilaci do bytecode. Syntaxe je ponekud odlisna od C, ale presto pomerne prehledna, pokud se ji clovek nauci.
    </p>

    [] Miloslav Ponkrác 11. září 2003
    [ + ] 0 [0x] [ - ] ← pro ohodnocení komentáře se není nutné nikde registrovat
    → [/-/627] ← na komentář můžete odpovědět nebo ho sdílet
    Máte tam chybu v makru duplicate, chybi alokovat 1 bajt pro zaverecnou nulu. Uz jsem na to upozornoval pred nekolika mesici.

    [] xChaos 5. listopadu 2003 ← komentářů 5520 0 [3050x]
    [ + ] 0 [0x] [ - ] ← pro ohodnocení komentáře se není nutné nikde registrovat
    → [/-/641] ← na komentář můžete odpovědět nebo ho sdílet
    Ponkrác: jo jo, v nové verzi C<<1 je to už opravené, taky jsem na to přišel
     

    Počet zobrazených komentářů: 3 [celkový čas potřebný k prohledání databáze a vytvoření stránky: 1.36 sekund]

    Pozor, vložením komentáře souhlasíte s pravidly hry TečkyCZ! [zobrazit pravidla] →
    Ochrana proti spambotům - tři-krát-tři je ... ? (napište číslicí - nemělo by byt potřeba při zapnutém JavaScriptu)
    Sociální síť (přihlaste se předem, 1. řádek<=96 znaků=status, zbytek=odkaz)
    Offtopic resolver (týká se odpověď původního tématu, nebo patří jinam?)
    Přezdívka (povinně) - nepoužívejte speciální znaky, mezery=podtržítka
    E-mail (volitelně) - nebude zobrazen, zobrazí se ikonka z [www.gravatar.com]

    Nelze použít HTML [zobrazit mikrosyntax] →

    Nápověda: ve vlastním zájmu uvádějte u komentářů pouze funkční a dostupnou e-mailovou adresu. Přezdívku, která je jednou spojená s konkrétní e-mailovou adresou, už nyní nelze bez zásahu administrátora serveru spojit s jinou adresou. Uvedením neplatné e-mailové adresy si v budoucnu znemožníte upload ikonky i možnost použít některé další chystané neanonymní funkce vázané na uvedení platné e-mailové adresy.


    TečkaCZ [Nejnovější články] [Nejnovější komentáře] [Zeď vzkazů] [Zeď odkazů] [Začátek článku]

        TečkaCZ
    •  
    • Komentáře →
    • Nástěnky →
    • Debaty →
    • Články →
    • Ročníky →
    • Rubriky →
    • RSS kanály →
    • Vzhled →
    • Ostatní →
    •  
    [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] []
    •  
    .

    [Arachne Labs]

    [SPOJE.NET]

    [Právě dnes | Tech | Ostatní]

    Yacy P2P web search jabber.arachne.cz
    tiskové zprávy a otevřené dopisy přebíráme z nejrůznějších informačních kanálů (i bez výslovného souhlasu autorů)
    licenční práva k použitým obrázkům a grafickým motivům nejsou definována (přebírejte pouze texty bez obrázků)
    texty článků i komentáře bez uvedení copyrightu jsou chráněny GNU Free Documentation License
    založeno na Quzo engine, (G)1999-2002 David Čermák, (G)2002-2012 Michael Polák
    Quzo engine vyvíjejí Arachne Labs, webhosting sponzorují SPOJE.NET
    seznam aktuálních článků je dostupný i ve formátu RSS (XML)
    můžete také sledovat Twitter feed TečkyCZ.
    test XHTML a CSS2 validity