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



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





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