diff options
Diffstat (limited to 'Build/source/utils/vlna/vlna-src/vlna.w')
-rw-r--r-- | Build/source/utils/vlna/vlna-src/vlna.w | 1070 |
1 files changed, 1070 insertions, 0 deletions
diff --git a/Build/source/utils/vlna/vlna-src/vlna.w b/Build/source/utils/vlna/vlna-src/vlna.w new file mode 100644 index 00000000000..8062a128b50 --- /dev/null +++ b/Build/source/utils/vlna/vlna-src/vlna.w @@ -0,0 +1,1070 @@ + +\input csmac % Makra pro češtinu +\pageheight=9.5in \fullpageheight=9.8in \setpage +%\nocon % omit table of contents +\datethis % print date on listing + +\def\begitems{\medskip\bgroup\catcode`\*=13 \narrower\narrower} +\def\enditems{\par\egroup\medskip} +{\catcode`\*=13 \gdef*{\par\noindent\llap{$\bullet$\ }\ignorespaces}} + + +@* PROGRAM VLNA. +Program čte vstupní textový soubor a nahrazuje za specifikovanými +jednopísmennými slovy (např.~v, k, u) mezery symbolem \uv{\.{\char126}}. To +zabrání při následném zpracování \TeX{}em zlomit řádek na nevhodných +místech, která jsou v rozporu s typografickou normou. + +Program sestává z těchto hlavních celků: +@c +@<Hlavičkové soubory k načtení@>@/ +@<Globální deklarace@>@/ +@<Pomocné funkce@>@/ +@<Vlnkovací funkce |tie|@>@/ +@<Hlavní program@> + +@ Definujeme |BANNER|, což je text, který se objevi při startu +programu a obsahuje číslo verze programu. +Zde je názorně vidět, že míchání dvou jazyků se nevyhneme. Při tisku +textů na terminál nesmíme předpokládat, že tam budou české fonty. +V~této dokumentaci se setkáme se třemi jazyky: angličtinou (většinou +v~kódu programu, cestinou v~/* komentářích */ a češtinou jinde. +Tu cestinu si vynutil fakt, že DOS-ovská varianta \.{tangle} a +\.{weave} se nesnáší s~akcentovanými písmeny v~/* komentářích */. +A~nyní už slíbený (vícejazyčný) |BANNER|. +@d BANNER "This is program vlna, version 1.5, (c) 1995, 2002, 2009, 2010 Petr Olsak\n" + +@ V programu jsou použity knihovní funkce, jejíchž prototypy jsou +definovány ve třech standardních hlavičkových souborech. +@<Hlavičkové ...@>= +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +@ Definujeme konstanty pro návratový kód. |OK| pro úspěšný běh, +|WARNING| při výskytu aspoň jedné varovné zprávy, |IO_ERR| pro chybu +v~přístupu ke vtupním nebo výstupním souborům, |BAD_OPTIONS| pro +syntaktickou chybu na příkazové řádce a |BAD_PROGRAM| pro případ +havárie programu. Ta by neměla nikdy nastat. Proměnná |status| bude +obsahovat návratový kód a proměnná |prog_name| bude ukazovat na text +nultého parametru příkazové řádky. +@d OK 0 +@d WARNING 1 +@d IO_ERR 2 +@d BAD_OPTIONS 3 +@d BAD_PROGRAM 4 +@<Globální deklarace@>= +char *prog_name; +int status; + +@ Základní rozvržení funkce |main|. +@<Hlavní program@>= +int main (int argc, char **argv) +{ + @<Lokální proměnné funkce |main|@>; + prog_name=argv[0]; status = OK; + @<Načtení parametrů příkazového řádku@>; + if (!silent) fprintf (stderr, BANNER); + @<Inicializace datových struktur@>; + @<Zpracování souborů@>; + return status; +} + +@* Parametry příkazového řádku. +Program čte z~příkazového řádku postupně (nepovinné) parametry, +které začínají znakem \uv{\.{-}}. Pak následují jména vstupních a výstupních +souborů. +\begitems +* \.{-f} \dots\ program pracuje jako filtr (viz sekce |@<Zpracování + souborů@>|). Není-li tento parametr použit, program pracuje v tzv. + standardním režimu, kdy jednotlivé soubory jsou vstupní i výstupní. +* \.{-s} \dots\ program nevypíše |BANNER|, ani sumarizaci, ani varování, + při nichž není program předčasně ukončen. Všechny tyto výpisy + směřují do |stderr|, takže pokud program pracuje v režimu \uv{filtr}, + není nutné tento parametr použít. +* \.{-r} \dots\ program maže pracovní soubor (soubory), které vytváří + ve standardním režimu (tj. není použit \.{-f}). V reřimu filter nemá + tento parametr vliv. +* \.{-v} \dots\ parametr definuje skupinu písmen, které budou + interpretovány jako neslabičné předložky. + Např. \.{-v KkSsVvZzOoUuAI}. Pokud není parametr uveden, je použita + skupina uvedená v tomto příkladě. +* \.{-x} \dots\ parametr vymezuje pomocí hexadecimálního zápisu string, + který program vkládá na vyhledaná místa. Implicitně vkládá vlnku. + Například \.{-x C2A0} způsobí, že program bude vkládat místo vlnky dva byty, + první s kódem \.{C2} a druhý s kódem \.{A0}. +* \.{-m} \dots\ program neprovádí kontrolu math/text módů, tj. vlnkuje i + uvnitř matematického módu \TeX{}u. (Implicite tam nevlnkuje). +* \.{-n} \dots\ prorgram neprovádí kontrolu verbatim módu, tj. vlnkuje i + uvnitř verbatim módu definovaném běžnými prostředími. Imlicite ve + verbatim prostředí nevlnkuje. +* \.{-l} \dots\ La\TeX{} režim. Při kontrole text-math-verbatim módů jsou + brány v úvahu další sekvence, obvyklé v La\TeX{}ových dokumentech. +* \.{-w} \dots\ WEB režim. Ohraničení verbatim módu je doplněno znaky + používanými v dokumentech WEB (např. tento dokument). Důsledek: program + vlnkuje dokumentační část každé sekce, ale nikoli kód. +\enditems + +Definujeme funkci |printusage|, která tiskne (při chybě) stručný přehled +možných parametrů. Nepodařilo se mi zjistit, jak se ve WEBu napíše +kulturně dlouhý string obsahující \.{\char92n} s formátovacími +požadavky. Byl jsem nucen to takto nehezky zapsat. +@<Pomocné funkce@>= +static void printusage (void) +{ + fprintf(stderr, + "usage: vlna [opt] [filenames]\n" + " opt -f : filter mode: file1 file2 ... file1->file2\n" + " file1 ... file1->stdout\n" + " ... stdin->stdout\n" + " nofilter: file1 [file2 file3 ...] all are in/out\n" + " -s : silent: no messages to stderr\n" + " -r : rmbackup: if nofilter, removes temporary files\n" + " -v charset : set of lettres to add tie, default: KkSsVvZzOoUuAI\n" + " -x code : code for tie symbol, default: 7E, example -x C2A0\n" + " -m : nomath: ignores math modes\n" + " -n : noverb: ignores verbatim modes\n" + " -l : LaTeX mode\n" + " -w : web mode\n"); +} + +@ Proměnné |isfilter|, |silent|, |rmbackup|, |nomath|, |noverb|, +|latex|, resp. |web| říkají, že je nastaven parametr \.{-f}, \.{-s}, +\.{-r}, \.{-m}, \.{-n}, \.{-l}, resp. \.{-w}. Proměnná |charset| +ukazuje buď na implicitní skupinu znaků |charsetdefault|, nebo (při +použití parametru \.{-v}) na text uvedený v příkazovém řádku. +@<Globální deklarace@>= +int isfilter=0, silent=0, rmbackup=0, nomath=0, noverb=0, web=0, latex=0; +char charsetdefault[]="KkSsVvZzOoUuAI"; +char *charset=charsetdefault; + +@ String |tiestr| obsahuje string, kterým se má nahradit vyhledané +místo. Pokud není použit parametr \.{-u}, je tento string jadnoznakový +a obsahuje vlnku. Jinak obsahuje string konvertovaný z parametru \.{-u}. +String má délku |tiestrlen| bez ohledu na to, zda obsahuje nebo +neobsahuje nulové znaky (C-čková konvence pro stringy není použita). +@<Globální deklarace@>= +unsigned char tiestr[MAXLEN]; +int tiestrlen; + +@ @<Načtení parametrů ...@>= +tiestr[0] = '~'; +tiestrlen = 1; +while (argc>1 && argv[1][0] == '-') { + if (argv[1][2] != 0) printusage (), exit (BAD_OPTIONS); + switch(argv[1][1]) { + case 'f': isfilter = 1; break; + case 's': silent = 1; break; + case 'r': rmbackup = 1; break; + case 'v': if (argc<2) printusage (), exit (BAD_OPTIONS); + argv++; argc--; charset = argv[1]; break; + case 'x': if (argc<2) printusage (), exit (BAD_OPTIONS); + argv++; argc--; settiestr(argv[1]); break; + case 'm': nomath = 1; break; + case 'n': noverb = 1; break; + case 'l': latex = 1; break; + case 'w': web = 1; break; + default: printusage (), exit (BAD_OPTIONS); + /* nezn\'am\'y parametr */ + } + argc--; argv++; +} + +@ Vyřešíme konverzi kódu zapsaného za parametrem \.{-x} na string |tiestr|. +@<Pomocné funkce@>= +static unsigned char hexnum(char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + printusage (), exit (BAD_OPTIONS); +} +static void settiestr(char *s) { + int i, j; + i = strlen(s); + if ((i > 2*MAXLEN) || i%2 == 1) printusage (), exit (BAD_OPTIONS); + tiestrlen = i/2; + j = 0; + for (i=0; i<tiestrlen; i++) { + tiestr[i] = hexnum(s[j++]) << 4; + tiestr[i] += hexnum(s[j++]); + } +} + +@* Zpracování souborů. Parametr |MAXLEN| definuje maximální možnou +délku jména souboru, který vytvoříme jako přechodný, nebo zálohový. +Dále deklarujeme proměnné typu \uv{stream}. +@d MAXLEN 120 +@<Lokální proměnné funkce...@>= +FILE *infile, *outfile; +char backup[MAXLEN]; +int j; + +@ Definujeme funkci pro výpis chybového hlášení při neúspěšném otevření +souboru. +@<Pomocné funkce@>= +static void ioerr (char *f) +{ + fprintf(stderr, "%s: cannot open file %s\n", prog_name, f); +} + +@ Způsob zpracování souborů rozlišíme podle režimu daným přepínačem \.{-f}. +@<Zpracování souborů@>= +if (isfilter) @<Zpracování v režimu filter@> @/ +else @<Zpracování všech souborů příkazové řádky@> + +@ V režimu |isfilter==1| je další zpracování závislé na počtu souborů v +příkazové řádce: +\begitems +* nula souborů -- vstup je |stdin| a výstup je |stdout|, +* jeden soubor -- je vstupní, výstup je |stdout|, +* dva soubory -- první je vstupní, druhý výstupní, +* více souborů -- program skončí s chybou. +\enditems +@<Zpracování v režimu filter@>= +{ + if (argc > 3) printusage (), exit (BAD_OPTIONS) ; + infile = stdin; outfile = stdout; + if (argc >= 2) infile = fopen (argv[1], "r"); + if (infile == NULL) ioerr (argv[1]), exit (IO_ERR); + if (argc == 3) outfile = fopen(argv[2], "wb"); + if (outfile == NULL) ioerr (argv[2]), exit (IO_ERR); + if (argc >= 2) filename = argv[1]; + else filename = NULL; + tie (infile, outfile); + if (outfile != stdout) fclose (outfile); + if (infile != stdin) fclose (infile); +} + +@ V~režimu |isfilter==0| jsou jednotlivé soubory v~příkazovém řádku +interpretovány jako vstupní i výstupní. Více souborů v~příkazovém řádku má +stejný efekt, jako opakované volání programu na jednotlivé soubory. +V~\UNIX/u lze tedy např. napsat \.{\jobname\ *.tex} a program doplní vlnky do +všech souborů s~příponou~\.{tex}. Toto neplatí v~DOSu, protože interpretace +masky je v~\UNIX/u starostí shellu a nikoli programu samotného. Náš program +masku nebude interpretovat. Je-li v~tomto režimu nulový počet souborů, +program se ukončí s~chybou. +@<Zpracování všech souborů příkazové řádky@>= +{ + if (argc==1) printusage (), exit(BAD_OPTIONS); + while (argc>1) { + argc--; argv++; + @<Přejmenuj vstup |argv[0]| na |backup| a otevři jej jako |infile|@>; + if (infile == NULL) { + ioerr (argv[0]); continue; + } + outfile = fopen (argv[0], "wb"); + if (outfile == NULL) { + ioerr (argv[0]); + rename (backup, argv[0]); + status = WARNING; + continue; + } + filename = argv[0]; + tie (infile, outfile); + fclose (outfile), fclose (infile); + if (rmbackup) remove (backup); + } +} + +@ Při |isfilter==0| program přejmenuje každý zpracovávaný soubor tak, že +změní poslední písmeno názvu souboru na znak \.{\char126}. Tento +přejmenovaný soubor bude otevřen jako vstupní a výstupem bude původní +soubor. Vstupní soubor při |rmbackup==0| zůstane zachován jako záloha. + +Proč vlnku nepřidáváme na konec názvu souboru, ale měníme ji za poslední +znak souboru? Protože chceme, aby program fungoval i v tak nemožných +systémech, jako je DOS. +@<Přejmenuj vstup...@>= +infile = NULL; +j = strlen (argv[0]) - 1; +if (j >= MAXLEN || argv[0][j] == '~') { + if (!silent) fprintf (stderr, "%s: the conflict of file name %s\n", + prog_name, argv[0]); +} +else { + strcpy (backup, argv[0]); + backup[j] = '~'; + remove (backup); + j = rename (argv[0], backup); + if (j == 0) infile = fopen (backup, "r"); +} + +@* Patterny. Abychom mohli účelně definovat chování programu +v~různých situacích, zavedeme datovou strukturu |PATTERN|. Zhruba +řečeno, budeme sledovat vstup znak po znaku a pokud bude část vstupu +souhlasit s~definovaným patternem, provedeme námi požadovanou +akci. Například nejčastější aktivitu, přidání vlnky uvnitř řádku, +spustíme v~okamžiku, kdy vstupní text odpovídá patternu \uv{\.{\ (v\ +p}}, kde \uv{\.{\ }} znamená jedna nebo více mezer a tabelátorů, +\uv{\.{(}} je nula nebo více otevíracích závorek všeho druhu, +\uv{\.{v}} znamená jedno písmeno z~množiny předložek (viz |charset|) a +\uv{\.{p}} zde znamená libovolné písmeno. Příklad zde není zcela přesný. +Přesně jsou všechny patterny pro náš program definovány v~závěrečných +sekcích tohoto povídání. + +Pattern bude znamenat konečnou sekvenci tzv. pozic patternu (|PATITEM|). +Cykly uvnitř pozic pro jednoduchost nepřipustíme. Každá pozice obsahuje +řetězec znaků, uvažovaný pro danou pozici (v~příkladu pozice~\uv{\.{\ }} by +obsahovala mezeru a tabelátor, zatímco pozice \.{v} odpovídá |charset|). +Každá pozice má svůj přepínač (|flag|), který obsahuje informaci o~tom, +zda shodu testovaného znaku s~některým prvkem v~množině znaků +budeme považovat za úspěch či neúspěch a zda pozice se ve zkoumaném +řetězci může vyskytovat právě jednou nebo opakovaně. Jako druhý případ +stačí implementovat \uv{nula nebo více} protože \uv{jedna nebo více} lze +popsat pomocí dvou pozic, první \uv{právě jednou} a následující \uv{nula +nebo více}. Jednotlivé pozice jsou zřetězeny ukazatelem |next|, poslední +pozice má |next==NULL|. Stejně tak jednotlivé patterny budeme +sestavovat do seznamů a budou rovněž zřetězeny ukazatelem |next|. + +Pattern kromě řetězu pozic obsahuje ukazatel na funkci (proceduru) |proc|, +která se má vykonat v~případě, že testovaný řetězec vyhovuje patternu. + +@d ONE 1 /* flag: prave jeden vyskyt */ +@d ANY 2 /* flag: nula nebo vice */ +@d ONE_NOT -1 /* flag: prave jednou, znak nesmi byt v mnozine */ +@d ANY_NOT -2 /* flag: nula nebo vice, znak nesmi byt v mnozine */ + +@<Globální deklarace@>= +typedef struct PATITEM { /* jedna pozice patternu */ + char *str; /* seznam znaku na teto pozici */ + int flag; /* vyznam seznamu znaku */ + struct PATITEM *next ; /* nasledujici pozice patternu */ +} PATITEM; +typedef struct PATTERN { /* jeden pattern */ + PATITEM *patt; /* ukazatel na prvni pozici */ + void (*proc)(void); /* procedura spustena pri souhlasu patternu */ + struct PATTERN *next ; /* nasledujici v seznamu vsech patternu */ +} PATTERN; + +@ Deklarujeme některé globální proměnné pro práci s~patterny. |lapi| je pole +obsahující ukazatele na aktuální pozice v~otevřených patternech. Říkáme, +že \uv{pattern je otevřen}, pokud zkoumaný řetězec s~ním {\it začíná\/} +souhlasit. Pattern se uzavře, pokud nastane jedna ze dvou možností: +zkoumaný řetězec s~mím souhlasí až do konce (v~takovém případě se provede +procedura |proc|), nebo při vyšetřování dalších znaků ze zkoumaného +řetězce přestane řetězec s~patternem souhlasit. + +V~dané chvíli může být pattern otevřen několikrát. Např. pattern \.{abac} +je při stringu \.{aba} při výskytu druhého \.{a} otevřen podruhé. Proto +pole obsahuje ukazatele na právě aktuální pozici patternu a nikoli na +pattern jako takový. + +V~poli |lapi| budou na počátku samá |NULL| (to se při překladu inicializuje +samo) a přemazání ukazatele na pozici konstantou |NULL| budeme považovat +za zavření patternu. Vedle pole |lapi| souměrně udržujeme pole |lapt|, +do něhož budeme ukládat ukazatele na odpovídající otevřený pattern. Tuto +informaci použijeme v~případě, že potřebujeme např, znát |proc| +patternu. + +|listpatt| bude ukazovat na začátek aktuálního seznamu patternů. Seznamy +budeme mít dva. Jeden se použije, nacházíme-li se mimo komentář a druhý +v~případě, že se nacházíme v~prostoru \TeX{}ovského komentáře (tj. za +procentem). Starty těchto seznamů patternů jsou |normallist| a +|commentlist| a aktivní |listpatt| má vždy jednu z~těchto dvou hodnot. + +Proměnné |lastpt| a |lastpi| použijeme pro budování řetězové struktury +patternů. + +Proměnná |c| obsahuje právě testovaný znak ze vstupu (který se rovněž +přepíše do bufferu |buff|). Z~bufferu občas ukládáme data do výstupního +proudu. Děláme to ale vždy jen v~okamžiku, kdy není otevřen žádný +pattern. Tehdy totiž \uv{nehrozí} situace, že by nějaká procedura vyvolaná +souhlasem patternu požadovala v~tomto bufferu nějaké změny se zpětnou +platností. O~vyprázdnění bufferu se začneme zajímat až v~okamžiku, kdy je +zaplněn aspoň na hodnotu |BUFI|, abychom proceduru přepisu bufferu do +výstupního proudu neaktivovali zbytečně často. +@d MAXPATT 200 /* maximalni pocet patternu */ +@d MAXBUFF 500 /* velikost bufferu pro operace */ +@d BUFI 300 /* velikost stredniho zaplneni */ +@<Globální deklarace@>= +PATITEM *lapi[MAXPATT]; /* pole ukazatelu na aktualni pozice */ +PATTERN *lapt[MAXPATT]; /* pole odpovidajicich ukazatelu na patterny */ +PATTERN *listpatt, *normallist, *commentlist, *pt, *lastpt=NULL; +PATITEM *lastpi=NULL; +char c; /* zrovna nacetny znak */ +char buff[MAXBUFF]; /* prechodny buffer */ +int ind; /* aktualni pozice prechodneho bufferu */ + +@ Dne 30. 4. 2009 jsem přidal možnost čtení vstupu, který obsahuje nulové byty. +Takové nuly se přepisují do výstupu, ale program si jich nevšímá při +procházení patternů. Tím je možno program použít na soubory kódované +v UTF16, ačkoli patterny obsahují jen jednobytové ASCII znaky. +Buffer |buff| může obsahovat i nulové byty, které je třeba přepsat do výstupu. +Na druhé straně buffer |buffnz| obsahuje jen nenulové byty, na které se +někdy ptáme při pohledu dozadu. Nejdelší pohled dozadu je o čtyři byty. +Udělám tedy |buffnz| osmibytový, začnu jej plnit od |buffnz|[4] +a kdykoli je buffer zcela zaplněn, přesunu horní čtyři byty na spodní a dále +pokračuji v plnění bufferu od pozice |buffnz|[4]. +@<Globální deklarace@>= +char buffnz[8]; +int inz; + +@ Nyní definujeme pomocné funkce |setpattern|, |setpi| a |normalpattern|. +Tyto funkce alokují paměť pomocí standardní funkce |malloc|. Abychom mohli +ohlídat případnou chybu při alokaci, budeme allokovat paměť zprostředkovaně +pomocí funkce |myalloc|. +@<Pomocné funkce@>= +static void *myalloc (int size) +{ + void *p; + p = malloc (size); + if (p == NULL) + { + fprintf (stderr, "%s, no memory, malloc failed\n", prog_name); + exit (BAD_PROGRAM) ; + } + return p; +} + +@ Funkce |setpattern| alokuje paměťové místo struktury |PATTERN| a napojí +ji pomocí proměnné |lastpt| na už alokovaný řetěz patternů. +Vrátí ukazatel na nově alokované místo. Jednotlivé pozice patternu se musí +následovně alokovat pomocí |setpi|. +@<Pomocné funkce@>= +static PATTERN *setpattern (void (*proc)(void)) +{ + PATTERN *pp; + pp = myalloc (sizeof (PATTERN)); + pp->proc = proc; + pp->next = NULL; + pp->patt = NULL; + if (lastpt != NULL) lastpt->next = pp; + lastpt = pp; + lastpi = NULL; + return pp; +} + +@ Funkce |setpi| alokuje paměťové místo pro jednu pozici patternu. Provede +zřetězení tak, aby první pozice řetězu pozic byla zaznamenána v položce +|patt| ve struktuře |PATTERN| a další byly provázány položkou |next| ve +struktuře |PATITEM|. Poslední pozice má |next==NULL|. +@<Pomocné funkce@>= +static void setpi (char *str, int flag) +{ + PATITEM* p; + p = myalloc (sizeof (PATITEM)); + p->str = str; p->flag = flag; + p->next = NULL; + if (lastpi == NULL) lastpt->patt = p; + else lastpi->next = p; + lastpi = p; +} + +@ Připravme si půdu pro funkci |normalpattern|. Tato funkce alokuje +strukturu pro jeden pattern včetně pozic patternu na základě vstupního +stringu. Každá pozice patternu obsahuje v~množině znaků jediný znak a má +|flag=ONE|. Znaky ve vstupním stringu odpovídají po řadě jednotlivým +pozicím. Vytvoří se vlastně jakýsi absolutní pattern, tj. testovaný řetězec +se musí přesně shodovat s~uvedeným stringem. Výjimku tvoří znak |"."|, +který se interpretuje jako nula nebo více mezer. Chceme-li tečku +vnutit do patternu, napíšeme dvě tečky za sebou. + +Nejdříve deklarujeme pole všech možných jednopísmenných stringů. +@<Globální deklarace@>= +char strings[512]; +int i; + +@ Inicializujeme toto pole (znak, nula, znak, nula, atd...). +@<Inicializace datových struktur@>= +for (i=0; i<256; i++) { + strings[2*i] = (char) i; strings[2*i+1] = 0; +} + +@ Definujme funkci |normalpattern|. +@<Pomocné funkce@>= +static PATTERN *normalpattern (void (*proc)(void), const char *str) +{ + PATTERN *pp; + int j=0; + pp = setpattern (proc); + while (str[j]) { + if (str[j]=='.') { + j++; + if (str[j]!='.') { + setpi (blankscr, ANY); + continue; + } + } + setpi (&strings[(unsigned char)str[j]*2], ONE); + j++; + } + return pp; +} + +@ Funkce |match|. Definujeme funkci, která na základě hodnoty znaku |c| +(proměnná |c| je definována jako globální), a pozice patternu |p| (parametr +funkce) vrátí informaci o tom, zda znak souhlasí s patternem. Záporná čísla +|FOUND|, resp. |NOFOUND| znamenají, že je třeba uzavřít pattern s tím, že +vzor odpovídá, resp. neodpovídá patternu. Nezáporné číslo vrátí v případě, +že zkoumaný vstup stále souhlasí s patternem, ale není ještě +rozhodnuto. Velikost návratové hodnoty v takovém případě udává, o kolik +pozic je třeba se posunout v patternu, abychom měli ukazatel na pozici +patternu v souhlase s novou situací, způsobenou znakem |c|. + +Pokud je |c| v množině znaků pro danou pozici |p->str|, bude |m==1|, jinak +je |m==-1|. Pokud tímto číslem pronásobíme hodnotu |p->flag|, nemusíme +větvení podle |p->flag| programovat dvakrát. Hodnoty |flag| jsou totiž +symetrické podle nuly, např. |ANY==-ANY_NOT|. +@d FOUND -1 +@d NOFOUND -2 +@<Pomocné funkce@>= +static int match (PATITEM *p) +{ + int m; + if (strchr (p->str, c) != NULL) m = 1; /* Znak nalezen */ + else m = -1; /* Znak nenalezen */ + switch (m * p->flag) { + case ANY: return 0; /* Souhas, neni nutny posun */ + case ONE: if (p->next == NULL) return FOUND; + return 1; /* Souhas, nutny posun o 1 */ + case ONE_NOT: return NOFOUND; /* Nesouhlas */ + case ANY_NOT: @<Vrať hodnotu podle následující...@>; + } + return 0; /* Tady bychom nikdy nemeli byt, return pro potlaceni varovani */ +} + +@ O kolik pozic je třeba se posunout a s jakým výsledkem zjistíme +rekurzivním voláním funkce |match|. +@<Vrať hodnotu podle následující pozice patternu@>= +switch (m = match (p->next)) { +case NOFOUND: return NOFOUND; +case FOUND: return FOUND; +default: return 1 + m; +} + +@* Vlnkovací funkce. +Nejprve připravíme globální deklarace pro \uv{vlnkovací} funkci |tie|. +Funkce |tie| \uv{ovlnkuje} vstupní soubor |infile| a vytvoří soubor +|outfile|. Při |silent=0| tiskne závěrečnou zprávu o zpracování. V této +zprávě se objeví jméno souboru, které se funkce \uv{dozví} prostřednictvím +globální proměnné |filename|. Proměnná |numline| počítá řádky, proměnná +|numchanges| sčítá změny, tj. počet doplněných vlnek. +Proměnná |mode| nabývý některé z hodnot |TEXTMODE|, |MATHMODE|, +|DISPLAYMODE| a |VERBMODE| podle stavu ve čteném textu. +@d TEXTMODE 0 +@d MATHMODE 1 +@d DISPLAYMODE 2 +@d VERBMODE 3 +@<Globální deklarace@>= +char *filename; /* jmeno zpracovavaneho souboru */ +long int numline, numchanges; /* pro zaverecnou statistiku */ +int mode; + +@ Nyní definujeme vlnkovací funkci |tie|. Veškerá činnost se opírá o +strukturu patternů. Výhodné je (z důvodu rychlosti) \uv{natvrdo} zde +implementovat jen přepínání mezi stavem čtení z oblasti komentáře +(|listpatt==commentlist|) a mimo komentář (|listpatt==normallist|); +@<Vlnkovací funkce |tie|@>= +static void tie (FILE *input, FILE *output) +{ + int ap; /* ap je pocet otevrenych patternu */ + register int k, m, n; + int ic; + PATTERN *pp; + PATITEM *pi; + + @<Inicializace proměnných při startu funkce |tie|@>; + + while (!feof(input)) { + if (ap == 0 && ind > BUFI && c !='\\') @<Vyprázdni buffer@>; + @<Otevři nové patterny@>; /* 1. 2. 2010: prohozene poradi */ + if (ind >= MAXBUFF) { + fprintf (stderr, "Operating buffer overflow, is anything wrong?\n"); + exit (BAD_PROGRAM); + } + if ((ic = getc(input)) == EOF) /* opravil Cejka Rudolf */ + break; + buff[ind++] = c = ic; + if (c == 0) continue; /* 30. 4. 2009 */ + if (inz>=8) { + for (inz=0; inz<4; inz++) buffnz[inz] = buffnz[inz+4]; + inz=4; + } + buffnz[inz++] = c; + if (c == '\n') numline++, listpatt = normallist; + if (c == '%' && mode!=VERBMODE && buffnz[inz-2] != '\\') listpatt = commentlist; + @<Projdi otevřené patterny@>; + } + @<Vyprázdni buffer@>; + if (!web) checkmode (); /* zaverecna kontrola modu */ + if (!silent) @<Tiskni závěrečnou zprávu@>; +} + +@ @<Inicializace proměnných při ...@>= +for (k=0; k<MAXPATT; k++) lapi[k] = NULL; +c = '\n'; +buff[0] = 1; mode = ap = 0; ind = 1; +for(inz=0; inz<4; inz++) buffnz[inz] = 0; +inz = 4; +numline = 1; numchanges = 0; +mode = TEXTMODE; + +@ Při manipulaci s bufferem byl použit jeden trik. Veškeré načtené znaky +začínají až od |buff[1]|, zatímco |buff[0]| je rovno nule. Je to proto, že +některé algoritmy se vrací o jeden znak zpět za svůj pattern, aby zjistily, +zda tam není symbol \uv{\.{\char92}} (například na výskyt sekvence +\.{\char92\char37} je třeba reagovat jinak, než na výskyt obyčejného +procenta). Kdybychm zazačali od |buff[0]|, v některých situacích +bychom se ptali, zda |buff[-1]=='\\'|, tj. sahali bychom na neošetřené +místo v paměti. Od 30. 4. 2009 tento problém pominul, protože se ptáme dozadu pouze +v~|buffnz|, ale vlastnost dříve implementovanou v |buff| jsem ponechal beze změny. +@<Vyprázdni buffer@>= +{ + fwrite (&buff[1], ind-1, 1, output); + ind = 1; +} + +@ Při procházení otevřenými patterny posunujeme v poli |lapi| pozice +jednotlivých patternů podle pokynů funkce |match|, případně pattern zavřeme +a případně vyvoláme proceduru patternu. + +Některé patterny v poli |lapi| už mohou být zavřeny, takže je nutno s tímto +polem pracovat jako s jakýmsi děravým sýrem. +@<Projdi otevřené patterny@>= +n = ap; k = 0; +while (n) { + while (lapi[k]==NULL) k++; /* zastav se na prvnim ukazateli na pattern */ + switch (m = match (lapi[k])) { + case FOUND: (*lapt[k]->proc)(); /* Pattern nalezen, spustit proceduru */ + case NOFOUND: lapi[k] = NULL; /* Deaktivace patternu */ + ap--; break; + default: while (m--) lapi[k] = lapi[k]->next; /* dalsi pozice patternu */ + } + k++; n--; +} + +@ Při otevírání nových patternů, které nejsou v tuto chvíli zablokovány, +se hned vypořádáme s takovými patterny, které nám dávají rovnou odpověď +typu |FOUND| nebo |NOFOUND|. V takových případech ani nezanášíme ukazatel +na pozici do pole |lapi|. +@<Otevři nové patterny@>= +pp = listpatt; +if (c) while (pp != NULL) { + switch (m = match (pp->patt)) { + case FOUND: (*pp->proc)(); /* spustit proceduru */ + case NOFOUND: break; + default: @<Vytvoř ukazatel na nový pattern a |break|@>; + } + pp=pp->next; +} + +@ Není-li hned známa odpověď, zda pattern vyhovuje či nikoli, +překontrolujeme nejdříve, zda už není pattern ve stejné pozici otevřený. +Pak najdeme první \uv{díru} v tabulce |lapi| a tam uhnízdíme nový ukazatel +na pozici v patternu. +@<Vytvoř ukazatel na nový pattern...@>= +pi = pp->patt; +while (m--) pi = pi->next; +n = ap; k = 0; +while (n) { + if (lapi[k]==pi) break; + if (lapi[k++] != NULL) n--; +} +if (!n) { + k = 0; + while (lapi[k] != NULL) k++; + if (k >= MAXPATT) { + fprintf (stderr, "I cannot allocate pp, is anything wrong?\n"); + exit (BAD_PROGRAM); + } + lapt[k] = pp; lapi[k] = pi; ap++; +} + +@ Poslední věcí ve funci |tie| je tisk závěrečné statistiky zpracování. +@<Tiskni závěrečnou zprávu@>= +fprintf (stderr, "~~~ file: %s\t lines: %ld, changes: %ld\n", + filename, numline, numchanges); + +@* Inicializace patternů. +Po vytvoření předchozího kódu opírajícího se o~patterny máme nyní v~ruce +poměrně silný nástroj na definování různých činností programu prostým +vytvořením patternu a příslušné jeho procedury. Pokud budeme chtít +v~budoucnu nějaký rys programu přidat, pravděpodobně to bude snadné. + +Nejprve deklarujeme některé často používané skupiny znaků v~patternech. + +@<Globální deklarace@>= +char tblanks[] = " ~\t"; +char blanks[] = " \t"; +char blankscr[] = " \t\n"; +char tblankscr[] = " ~\t\n"; +char nochar[] = "%~\n"; +char cr[] = "\n"; +char prefixes[] = "[({~"; +char dolar[] = "$"; +char backslash[] = "\\"; +char openbrace[] = "{"; +char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; +PATTERN *vlnkalist, *mathlist, *parcheck, *verblist ; + +@ Začneme definicí nejčastěji používaného patternu na vlnkování uvnitř +řádku. Připomeňme, že opakované volání funkce |setpattern| vytváří interně +seznam patternů, přičemž o~jejich propojení se nemusíme starat. Vyzvedneme +si z~návratového kódu funkce pouze ukazatel na první položku seznamu +|normallist|. Stejně tak opakované volání funkce |setpi| vytváří seznam +pozic pro naposledy deklarovaný pattern. +@<Inicializace datových struktur@>= +vlnkalist = setpattern (vlnkain); +setpi (tblankscr, ONE); +setpi (tblanks, ANY); +setpi (prefixes, ANY); +setpi (charset, ONE); +setpi (blanks, ONE); +setpi (blanks, ANY); +setpi (nochar, ONE_NOT); + +@ @<Inicializace proměnných při ...@>= +listpatt = normallist = vlnkalist; + +@ Vložení vlnky znamená vykonat následující práci: Zapamatovat si znak za skupinou mezer +(do proměnné |p|). Pokud před tímto znakem předchází nulový byte, použijeme ho později, proto +si jej uložíme do proměnné |z|. Dále se posuneme v bufferu vlevo přes všechny mezery, tabelátory +(přesněji |blanks|) a přeskakujeme přitom všechny nulové byty. Index |ind| se zastaví +na předložce. Posuneme jej doprava za předložku (++|ind|) a pokud tam je nulový byte a první znak +|tiestr| není nulový, posuneme se až za tento nulový byte. Dále vložíme string |tiestr|, +neboli vlnku. Nakonec připojíme zapamatovaný znak |p|, ovšem pokud před ním byla nula, +vložíme ji ještě před znak |p|. +@<Pomocné funkce@>= +static void vlnkain(void) +{ + int i; + char p, z; + ind--; + p = buff[ind--]; + z = buff[ind]; + while (!buff[ind] || (strchr(blanks, buff[ind]) !=NULL)) ind--; + if (!buff[++ind] && tiestr[0]) ind++; + for (i=0; i<tiestrlen; i++) buff[ind++] = tiestr[i]; + i = tiestrlen; /* nulu pred p vlozime, pokud je z==0 a */ + if (!tiestr[0]) i--; /* pocet vlozenych znaku z tiestr */ + if (!z && (i%2)) buff[ind++] = 0; /* je sudy */ + buff[ind++] = p; + numchanges++; +} + +@ Podobně pro tvorbu vlnky \uv{přes řádek} vytvoříme pattern a kód +procedury. +@<Inicializace dat...@>= +setpattern (vlnkacr); +setpi (tblankscr, ONE); +setpi (tblanks, ANY); +setpi (prefixes, ANY); +setpi (charset, ONE); +setpi (blanks, ANY); +setpi (cr, ONE); +setpi (blanks, ANY); +setpi (nochar, ONE_NOT); + +@ V proceduře k tomuto patternu musíme ošetřit případ typu +\uv{\.{a\char126v\char92np}}, +kdy nelze prostě přehodit \uv{\.{\char92n}} za \uv{\.{v}}, protože +bychom roztrhli +mezeru svázanou vlnkou už dříve. Proto musíme vyhledat vhodné místo pro +roztržení řádku, které bude až {\it před\/} znakem \uv{\.{a}}. Při důsledném +ošetření tohoto fenoménu můžeme dokonce narazit na situaci +\uv{\.{\char92n\ v\char126v\char126v\char92np}}, kde nemůžeme vložit +\uv{\.{\char92n}} před první výskyt \uv{\.{v}}, protože bychom dostali +\uv{\.{\char92n\char92n}}, tedy prázdný +řádek. Ten je v \TeX{}u interperetován odlišně. V této výjimečné +situaci pouze zrušíme stávající (v pořadí druhé) \uv{\.{\char92n}} a +nebudeme vytvářet nové. Na výstupu bude soubor o jeden řádek kratší. +@<Pomocné funkce@>= +static void vlnkacr(void) +{ + char p, z; + int i, j; + ind--; + p = buff[ind--]; + z = buff[ind]; + while (!buff[ind] || (strchr(blankscr, buff[ind]) !=NULL)) ind--; + i = ind; /* misto predlozky, kterou chceme vazat */ + while (i >= 0 && (strchr(blankscr, buff[i]) == NULL)) i--; + j = i; + while (i >= 0 && (!buff[ind] || (strchr(blanks, buff[i]) != NULL))) i--; + if (i >= 0 && buff[i] == '\n') j = -1; + if (j >= 0) buff[j] = '\n'; + else numline--; + if (!buff[++ind] && tiestr[0]) ind++; + for (i=0; i<tiestrlen; i++) buff[ind++] = tiestr[i]; + i = tiestrlen; + if (!tiestr[0]) i--; + if (!z && (i%2)) buff[ind++] = 0; + buff[ind++] = p; + numchanges++; +} + +@ Nyní vytvoříme patterny pro případy typu \.{\char92uv\char`\{v lese\char`\}}. +@<Inicializace dat...@>= +setpattern (vlnkain); /* na radku */ +setpi (tblankscr, ONE); +setpi (backslash, ONE); +setpi (letters, ONE); +setpi (letters, ANY); +setpi (openbrace, ONE); +setpi (prefixes, ANY); +setpi (charset, ONE); +setpi (blanks, ONE); +setpi (blanks, ANY); +setpi (nochar, ONE_NOT); + +setpattern (vlnkacr); /* pres radek */ +setpi (tblankscr, ONE); +setpi (backslash, ONE); +setpi (letters, ONE); +setpi (letters, ANY); +setpi (openbrace, ONE); +setpi (prefixes, ANY); +setpi (charset, ONE); +setpi (blanks, ANY); +setpi (cr, ONE); +setpi (blanks, ANY); +setpi (nochar, ONE_NOT); + + + +@ Vytvoříme patterny a proceduru pro potlatčení tvorby vlnky u písmen těsně +následujících sekvence \.{\char92TeX} a \.{\char92LaTeX}. Tj. nechceme, aby +např z textu \uv{\.{Vlastnosti~\char92TeX~u~jsou...}} jsme dostali text +s nesprávně vázaným písmenem +\uv{\.{Vlastnosti~\char92TeX~u\char126jsou...}}. +@<Inicializace dat...@>= +normalpattern (tielock, "\\TeX"); +setpi (blankscr, ONE); +normalpattern (tielock, "\\LaTeX"); +setpi (blankscr, ONE); + +@ Procedura |tielock| obsahuje nečistý trik. Při provádění procedury je +právě načten znak z |blankscr| a je uložen do |buff|. Testy na otevírání +nových patternů pro tento znak teprve budou následovat a testují se na +hodnotu proměnné |c|. Stačí tedy změnit hodnotu |c| a vlnkovací patterny se +neotevřou. +@<Pomocné funkce@>= +static void tielock (void) +{ + c = 1; +} + +@ Ošetříme nyní přechod do/z matematického režimu \TeX{}u. Uvnitř math +módu vlnky neděláme. Při zjištěném nesouladu v přechodech mezi +math-módy spustíme následující proceduru. +@<Pomocné funkce@>= +static void printwarning (void) +{ + if (!silent) + fprintf (stderr, + "~!~ warning: text/math/verb mode mismatch, file: %s, line: %ld\n", + filename, numline - (c=='\n'?1:0)); + status = WARNING; +} + +@ Začneme patterny pro přechod do/z matematického režimu, ohraničeného +jedním dolarem, nebo v La\TeX{}u příslušnými sekvencemi. Sekvence +La\TeX{}u \.{\char92(} a \.{\char92)} nejsou zahrnuty, protože bývají +často předefinovány k jiným užitečnějším věcem. +@<Inicializace datových ...@>= +if (!nomath) { + mathlist = setpattern (onedollar); + setpi (dolar, ONE); + setpi (dolar, ONE_NOT); + if (latex) { + normalpattern (mathin, "\\begin.{math}"); + normalpattern (mathout, "\\end.{math}"); + } +} + +@ @<Pomocné funkce@>= +static void mathin (void) +{ + if (mode!=TEXTMODE) printwarning (); + mode = MATHMODE; + normallist = listpatt = mathlist; +} +static void mathout (void) +{ + if (mode!=MATHMODE) printwarning (); + mode = TEXTMODE; + normallist = listpatt = vlnkalist; +} + +@ Při programování procedury |onedollar| nesmíme zapomenout na výskyt +sekvence \.{\char92\$}. V tom případě akci ignorujeme. Podobně u sekvence +\.{\$\$} souhlasí ten druhý dolar s naším patternem, ale to už jsme uvnitř +display módu. V takovém případě také nic neděláme. +@<Pomocné funkce@>= +static void onedollar (void) +{ + if (buffnz[inz-3]=='\\' || (buffnz[inz-3]=='$' && buffnz[inz-4]!='\\')) return; + if (mode==DISPLAYMODE) printwarning (); + else { + if (mode==TEXTMODE) mathin(); + else mathout(); + } +} + +@ Pokud najdeme prázdný řádek, překontrolujeme, zda náhodou nejsme v +math-módu. Pokud ano, vypíšeme varování a přejdeme do textového módu. +@<Inicializace dat...@>= +parcheck = setpattern (checkmode); +setpi (cr, ONE); +setpi (blanks, ANY); +setpi (cr, ONE); + +@ @<Pomocné funkce@>= +static void checkmode (void) +{ + if (mode!=TEXTMODE) { + printwarning (); + mode = TEXTMODE; + normallist = listpatt = vlnkalist; + } +} + +@ Nyní ošetříme výskyt dvou dolarů, tj. vstup do/z display módu. +Rovněž mysleme na La\TeX{}isty a jejich prostředí pro display-mód. Protože +je možná alternativa s hvězdičkou na konci názvu prostředí, raději už +uzavírací závorku do patternu nezahrnujeme. + +@<Inicializace dat...@>= +if (!nomath) { + normalpattern (twodollars, "$$"); + if (latex) { + normalpattern (displayin, "\\begin.{displaymath"); + normalpattern (displayin, "\\begin.{equation"); + normalpattern (displayout, "\\end.{displaymath"); + normalpattern (displayout, "\\end.{equation"); + } +} + +@ @<Pomocné funkce@>= +static void displayin (void) +{ + if (mode!=TEXTMODE) printwarning (); + mode = DISPLAYMODE; normallist = listpatt = parcheck; +} +static void displayout (void) +{ + if (mode!=DISPLAYMODE) printwarning(); + mode = TEXTMODE; normallist = listpatt = vlnkalist; +} +static void twodollars (void) +{ + if (buffnz[inz-3]=='\\') return; + if (mode==DISPLAYMODE) displayout (); + else displayin (); +} + +@ Následuje ošetření tzv. verbatim módu. Pro plain i La\TeX{} jsou nejčastější +závorky pro verbatim mod tyto (variantu s \.{\char92begtt} používám +s oblibou já). +@<Inicializace dat...@>= +if (!noverb) { + verblist = normalpattern (verbinchar, "\\verb"); + setpi (blankscr, ANY); + setpi (blankscr, ONE_NOT); + normalpattern (verbin, "\\begtt"); + if (latex) normalpattern (verbin, "\\begin.{verbatim"); +} +if (web) { + normalpattern (verbin, "@@<"); + normalpattern (verbin, "@@d"); +} +if (!noverb) { + verboutlist[0] = setpattern (verbout); + setpi (verbchar, ONE); + verboutlist[1] = normalpattern (verbout, "\\endtt"); + if (latex) verboutlist[2] = normalpattern (verbout, "\\end{verbatim"); +} +if (web) { + verboutlist[3] = normalpattern (verbout, "@@ "); + normalpattern (verbout, "@@*"); + normalpattern (verbout, "@@>|"); +} + + +@ Procedura |verbinchar| se od \uv{společné} procedury |verbin| liší v +tom, že zavede do stringu |verbchar| momentální hodnotu proměnné |c|. +Proto druhý výskyt této hodnoty verbatim režim ukončí. +@<Pomocné funkce@>= +int prevmode; +PATTERN *prevlist, *verboutlist[4]; +char verbchar[2]; +static void verbinchar (void) +{ + prevmode = mode; + verbchar[0] = c; + c = 1; + listpatt = normallist = verboutlist[0]; + prevlist = listpatt->next; + listpatt->next = NULL; + mode = VERBMODE; +} + +@ Při programování \uv{obecné} funkce |verbin| musíme dbát na to, aby +zůstal aktivní pouze odpovídající \uv{výstupní} pattern k danému +vstupnímu. Také si zapamatujeme mód, ze kterého jsme do verbatim +oblasti vstoupili, abychom se k němu mohli vrátit (např. uvnitř +math. módu může být +\.{\char92hbox} a v něm lokálně verbatim konstrukce). +@<Pomocné funkce@>= +static void verbin (void) +{ + int i; + i = 0; + prevmode = mode; + switch (c) { + case 't': i = 1; break; + case 'm': i = 2; break; + case '<': ; + case 'd': i = 3; + if (buffnz[inz-3]=='@@') return; /* dvojity @@ ignorovat */ + break; + } + listpatt = normallist = verboutlist[i]; + prevlist = listpatt->next; + if (c != '<' && c != 'd') listpatt->next = NULL; + mode = VERBMODE; +} + +@ @<Pomocné funkce@>= +static void verbout (void) +{ + if (mode!=VERBMODE) return; + if (web && buffnz[inz-2] == '@@' && buffnz[inz-3] == '@@') return; + mode = prevmode; + normallist->next = prevlist; + switch (mode) { + case DISPLAYMODE: normallist = listpatt = parcheck; break; + case MATHMODE: normallist = listpatt = mathlist; break ; + case TEXTMODE: normallist = listpatt = vlnkalist; break; + } +} + +@ Nyní implementujeme vlastnost dříve používaného programu vlnka, tj. že +lze jeho činnost vypnout a opět zapnout v komentářích. Vytváříme druhý +nezávislý seznam patternů a proto nejprve pronulujeme |lastpt|. +@<Inicializace dat...@>= +lastpt = 0; +commentlist = normalpattern (tieoff, "%.~.-"); +normalpattern (tieon, "%.~.+"); + +@ @<Pomocné funkce@>= +static void tieoff (void) +{ + normallist = NULL; +} +static void tieon (void) +{ + normallist = vlnkalist; +} + +@ Další plánovaná vylepšení. Program by mohl číst definici svého chování +nejen z~příkazové řádky, ale v~mnohem kompletnější podobě, včetně +uživatelsky definovaných patternů, z komentářové oblasti ve čteném souboru. +Parametry zde uvedené by mohly mít vyšší prioritu, než parametry +z~příkazové řádky a mohl by se třeba rozšiřovat seznam sekvencí, za nimiž +písmena nemají být vázana vlnkou (zatím je implemenováno na pevno jen +\.{\char92TeX} a \.{\char92LaTeX}). + +@* Rejstřík. + + |