Vypuštění komentářů – stavový automat (2015)
Zadání:
Napište program, který vypustí komentáře ze souboru (zdrojový nebo hlavičkový jazyka C/C++) a výsledek zapíše do jiného souboru. Úlohu vyřešte pomocí principu stavového automatu.
Upřesnění zadání:
Program vyřešte pomocí stavového automatu (použijte příkaz switch. K „pojmenování“ stavů použijte „textových“ hodnot enum (alternativa k použití #define)
Komentáře předpokládejte v obou tvarech (tj. „/* */“ i „ // “ ).
Znaky „komentářů“ se nevyskytují (=nemusíte řešit) v řetězcích a znakových konstantách. Nemusíte řešit trigrafy. Neřešte výskyt v makrech, ani blocích podmíněného překladu.
Název vstupního i výstupního souboru je předán jako parametr (na příkazové řádce) při spuštění programu.
Při otevírání souborů otestujte, zda se otevření zdařilo
Otevřené soubory zavřete
Pro stavy použijte výčtový typ enum.
Vypuštěný komentář /* */ nahraďte mezerou.
Nápověda řešení:
Princip stavového automatu se používá v situacích, kdy je možné
úlohu rozdělit na části/stavy, které splňují určité vlastnosti.
V
případě stavového automatu je nutné nalézt části programu tak,
abychom mohli určit (jednoznačné) přechody mezi jednotlivými stavy.
Stav je situace, kdy se program chová (na vstupy) stejně.
Ke
změně/přechodu stavu dochází při určité události/událostech na
vstupu.
Výhodou
zápisu pomocí stavového automatu je skutečnost, že jednotlivé stavy
jsou od sebe ve zdrojovém textu zcela izolovány (oproti vícenásobným
vnořením, při realizaci pomocí příkazu if) a umožňují tak
programátorovi lepší kontrolu nad zdrojovým textem a budoucí
rozšířitelnost stavového automatu.
Všimněte si, že realizace
pomocí stavového automatu nepotřebuje proměnné obsahující předchozí
hodnoty vstupu (historii vstupu), neboť samotný stav tuto skutečnost
plně reprezentuje.
Pro přechody mezi stavy je nutné určit
události/situace, za kterých k přechodu dochází (například
hodnota/hodnoty vstupu, časový signál...) a činnost, která se při
tomto přechodu provede.
Ke stejnému přechodu může dojít při více
typech vstupu (na určitou množinu situací/vstupů reaguje stejně).
Může docházet i k „přechodu“ do stejného stavu (pokud
se nestane něco výjimečného zůstáváme ve stejném stavu).
Pro náš případ vypouštění komentářů je důležité ponechat úseky
mimo komentáře (TEXT), zjistit začátek a konec komentáře, komentář
vypustit.
Můžeme tedy určit stavy TEXT, KOMENTAR1, KOMENTAR2 a
stavy přechodů.
Jako impulz pro činnost vezmeme znak ze vstupního
souboru, na jehož základě rozhodneme o přechodu („přepnutí“)
do jiného stavu a vykonávané činnosti.
Představme si, že načteme
znak '/'. Reakce na
tento znak bude v každém stavu jiná. V TEXTu to může být „normální“
znak (který se vytiskne), nebo začátek komentáře (který se netiskne)
– při příchodu tohoto znaku se tedy přepneme do nového stavu,
kdy další načtený znak rozhodne, která situace nastala (a zda se
vrátíme do stavu TEXT, nebo se přepneme do stavu KOMENTARX).
Přijde-li tento znak v okamžiku, kdy jsme v komentáři, potom ho
vypustíme. Je zde tedy vidět princip stavů – stejný podnět se v
různých stavech řeší různě, (někdy) má vliv na změnu stavu a (někdy)
na prováděnou akci.
Návrh (několika) stavů pro tento příklad:
TEXT – jsme uprostřed textu, který se kopíruje na výstup (objeví-li se lomítko, je nutno se přepnout do stavu, kdy lze očekávat komentář – lomítko se na výstup nezapíše)
LOMITKO – minulý znak bylo lomítko (podle aktuálního znaku poznáme, zda se jedná o /*, // nebo skutečně o lomítko
KOMENT1 – jsme uvnitř komentáře /*, další hvězdička je potencionální konec
HVEZDICKA – následuje-li po hvězdičce (v komentáři začínajícím /*) lomítko, končí komentář, je-li to jiný znak, vracíme se do KOMENT1
KOMENT2 – jsme uvnitř řádkového komentáře – končí koncem řádky (pak je TEXT)
…
Pro řešení stavového diagramu se s výhodou používá příkazů
switch/case realizující větvení na základě celočíselné proměnné
reprezentující akuální stav programu. Celočíselnou proměnnou (nejlépe
realizovanou pomocí výčtového typu enum) může být kód stavu, ve
kterém se nachází vyhodnocování. Podle aktuálně načteného znaku ze
souboru se stav může změnit a se znakem se provede příslušná akce (u
našeho příkladu buď kopie na výstup, nebo „vypuštění“).
Příkazy switch lze vkládat do sebe (přehlednost se dá zlepšit
použítím ohraničujících bloků {} a nebo pomocí funkcí, které sníží
počet řádků).
„První/Vnější“ příkaz switch přepíná větev programu na základě stavu. „Druhý/vnitřní“ switch přepíná na základě události/vstupu. V každé větvi bychom měli uvažovat zda dojde ke změně stavu a k jaké dojde činnosti. V některých stavech a situacích nemusí dojít ani ke změně stavu ani jiné činnosti.
Doporučený postup řešení:
založete nový konzolový projekt (jako základ zdrojových textů můžete vzít soubory z minulého cvičení)
v nastavení projektu ( command arguments) napište názvy vstupního a výstupního souboru. Mohlo by dojít ke kolizím při otevírání souborů. Jako projekt použijte prázdný bez precompiled headers. Pro testování používejte soubory, které nejsou součástí projektu!!! Testovací soubor si můžete vytvořit i z textu na konci tohoto zadání.
Soubor s main se bude jmenovat mainprj.c. Funkce budou v souboru funkce.c a příslušném funkce.h. Soubory se otevřou v main, do funkcí se předá FILE *.
Funkce main by měla mít parametry argc a argv. Vytiskněte je (a zkontrolujte, že mají očekávané hodnoty).
Pokuste se otevřít a zavřít vstupní a výstupní soubor. Otestujte, zda se operace povedly. (Otevření proveďte v textovém modu. K otevření použijte funkci fopen, k zavření fclose. Operace se povedla, pokud fopen nevrátí NULL.).
Zkuste soubor pouze překopírovat. (použijte fgetc a fputc. Obě funkce se chovají tak, že se po provedení posunou v souboru o znak. To je „ znak se pošle do linky a zmizí.“) Pro test konce souboru použijte funkci feof.
Nakreslete si stavový diagram – stavy a přechody mezi nimi (u přechodů určete kdy k nim dojde a jaká akce je provází)
Naprogramujte stavový automat.
Pro pokročilé
Doplňte program tak, aby řešil i:
- situaci, kdy se znak pro komentář objeví v řetězci. Např.: printf(“Komentář se píše takto: /* tady je komentář */ nebo takto: // tady je \“řádkový\“ komentář “);
- situaci, kdy by se ve výstupu nemělo objevit více prázdných řádků za sebou (např. když vypouštíme 10 jednořádkových komentářů za sebou). Po odstranění komentářů mohou v souboru zůstat rozsáhlejší úseky prázdných řádků. Na jejich odstranění je možné napsat jiný program, s podobným principem. Program by dostal opět názvy vstupního a výstupního souboru. Jako další parametr by mohl být uveden počet ponechaných prázdných řádků. Druhý program by mohl být použit jako zřetězení pomocí roury (neboli pipe). (příklad volání kom_destr.exe <zdroje.cpp | radky_destr.exe >zdroje_vyst.cpp)
Na adrese desatomat.cz je možné shlédnout vytvoření stavového automatu na základě zapsané gramatiky. Po kontrole stavového automatu je automaticky vygenerován i zdrojový kód C++, který tento automat realizuje. (klasický, bezkontextový, příklad pro Petyho).
Testovací text:
a
=
b
/
c;
d
=
a
/
*pi;
//
toto je komentar
// toto // je komentar
// toto je */
komentar
// toto je /* komentar
// toto je ** komentar *
/*
toto je komentar */
/* toto je komentar **/
/** toto je
komentar **/
/*** toto je komentar ***/
/* toto je * komentar
*/
/* toto je /* komentar */
/* toto je // komentar */
/*
toto je komentar *//* toto je komentar */
/* toto je komentar
*////* toto je komentar */
///* toto je komentar *//* toto je
komentar */
/* toto je /*** komentar */
/* toto je /** komentar
*/
a
=
g;
//
toto je komentar
b
=
c;
//
toto // je komentar
d
=
*pi;//
toto je */ komentar
a
=
/*
toto je komentar */
b;
h
=
/**
toto je komentar **/
*pi;
c
=
h
*/*
toto je komentar */
a;
c
=
h
//*
toto je komentar */ a;
*
e*=d;
Pro pokročilé [příklad z normy jazyka C: N1570 Committee Draft — April 12, 2011 ISO/IEC 9899:201x]
"a//b" // four-character string literal
#include "//e" // undefined behavior
// */ // comment, not syntax error
f = g/**//h; // equivalent tof = g / h;
//\
i(); // part of a two-line comment
/\
/ j(); // part of a two-line comment
#define glue(x,y) x##y
glue(/,/) k(); // syntax error, not comment
/*//*/ l(); // equivalent to l();
m = n//**/o
+ p; // equivalent tom = n + p;
Poslední změna: 2015-09-29