Różnice między wybraną wersją a wersją aktualną.
Both sides previous revision Previous revision Next revision | Previous revision | ||
interpreter [2008/12/07 11:18] posciak |
interpreter [2008/12/07 11:33] posciak |
||
---|---|---|---|
Linia 34: | Linia 34: | ||
=== Podstawowe kroki implementacji === | === Podstawowe kroki implementacji === | ||
- | * **Tworzenie klas wyrażeń** - każde wyrażenie terminalne i nieterminalne jest reprezentowane przez inną klasę pochodną od typu Expression (a właściwie od typów NonterminalExpression i TerminalExpression). Na przykład dla języka wyrażeń arytmetycznych powstaną m.in. klasy WyrażenieSuma, WyrażenieRóżnica pochodne od NonterminalExpression i WyrażenieLiczba, pochodne od klasy TerminalExpression. Tworzenie klas dla wszystkich możliwych wyrażeń terminalnych może być kłopotliwe ze względu na ich ilość, np. dla wyrażeń arytmetycznych zamiast tworzenia oddzielnej klasy dla każdej możliwej liczby, można stworzyć jedną klasę Liczba, zawierającą jej wartość. | + | * **Tworzenie klas wyrażeń** - każde wyrażenie terminalne i nieterminalne jest reprezentowane przez inną klasę pochodną od typu Expression (a właściwie od typów NonterminalExpression i TerminalExpression). Na przykład dla języka wyrażeń arytmetycznych mogą powstać m.in. klasy WyrażenieSuma, WyrażenieRóżnica - pochodne od NonterminalExpression, oraz WyrażenieLiczba - pochodne od klasy TerminalExpression. Należy wziąć pod uwagę, że tworzenie klas dla wszystkich możliwych wyrażeń terminalnych może być kłopotliwe ze względu na ich ilość, więc warto rozpatrzyć w tym przypadku stworzenie ogólniejszych klas, np. dla wyrażeń arytmetycznych zamiast oddzielnej klasy dla każdej możliwej liczby, można stworzyć jedną klasę Liczba, zawierającą jej wartość. |
* **Tworzenie klasy kontekstu** - w przypadku wyrażeń arytmetycznych powinien on przechowywać bieżącą (do tej pory obliczoną) wartość wyrażenia i zależności pomiędzy danym wyrażeniem, a jego sąsiadami. | * **Tworzenie klasy kontekstu** - w przypadku wyrażeń arytmetycznych powinien on przechowywać bieżącą (do tej pory obliczoną) wartość wyrażenia i zależności pomiędzy danym wyrażeniem, a jego sąsiadami. | ||
Linia 49: | Linia 49: | ||
===== Przydatność ===== | ===== Przydatność ===== | ||
Wzorzec interpretera działa najlepiej, gdy: | Wzorzec interpretera działa najlepiej, gdy: | ||
- | * gramatyka jest stosunkowo prosta - dla skomplikowanych gramatyk hierarchia klas staje się duża i trudna w utrzymaniu. | + | * gramatyka jest stosunkowo prosta - dla skomplikowanych gramatyk hierarchia klas staje się duża i trudna w utrzymaniu. |
- | * efektywność nie jest cechą krytyczną | + | * efektywność nie jest cechą krytyczną |
- | * istotna jest możliwość łatwej zmiany i rozszerzania gramatyki - wystarczy dodać nowe klasy do hierarchii lub zmodyfikować istniejące | + | * istotna jest możliwość łatwej zmiany i rozszerzania gramatyki - wystarczy dodać nowe klasy do hierarchii lub zmodyfikować istniejące |
===== Przykład ===== | ===== Przykład ===== | ||
+ | Prosty program wykorzystujący wzorzec interpretera do konwersji stringów zawierających liczby rzymskie na liczby typu integer. | ||
+ | Ponieważ system rzymski korzysta z różnych symboli dla poszczególnych potęg liczby 10, wyrażeniami są tutaj klasy reprezentujące różne zachowania dla Tysięcy, Setek, Dziesiątek i Jedności. Kontekst jest zrealizowany w sposób uproszczony: poprzez przekazywanie wejściowego ciągu znaków jako argumentu funkcji interpretuj() oraz zwracanie sumy danego wyrażenia. | ||
+ | W bardziej złożonej implementacji kontekst mógłby być zrealizowany przez stworzenie oddzielnej klasy, zawierającej dwa pola: //ciąg//, przechowującej aktualny ciąg wejściowy i //suma//, przechowującej wartość wyrażenia na danym etapie interpretacji. Klasa ta musiałaby być przekazywana do kolejnych klas wyrażeń, jako argument funkcji interpretuj(). | ||
+ | Również drzewo wyrażenia jest uproszczone i w tym przypadku jest ono po prostu listą. | ||
+ | |||
+ | <code cpp> | ||
+ | |||
+ | #include <iostream> | ||
+ | #include <string> | ||
+ | #include <boost/algorithm/string/predicate.hpp> | ||
+ | #include <boost/foreach.hpp> | ||
+ | #include <boost/ptr_container/ptr_list.hpp> | ||
+ | |||
+ | |||
+ | /** Reprezentuje (pod)wyrazenie, bedace czescia ciagu cyfr liczby rzymskiej. | ||
+ | * Kazde wyrazenie rozni sie aktualnym kontekstem, ktory jest | ||
+ | * analogia "miejsca dziesietnego" w danej liczbie rzymskiej. | ||
+ | * Od miejsca zaleza symbole dla poszczegolnych "cyfr" oraz mnoznik, czyli | ||
+ | * np. w kontekscie "setek" symbol "D" reprezentuje "piec" z mnoznikiem 100, | ||
+ | * czyli 500. | ||
+ | * W ogolnosci liczba rzymska sklada sie z cyfr, ktore | ||
+ | * sumuja sie do wartosci calej liczby, a takze z cyfr, ktore | ||
+ | * modyfikuja nastepujace po nich cyfry tworzac "podwojne" cyfry. | ||
+ | * (np. "IX" jest "podwojna" cyfra, a "V" pojedyncza). | ||
+ | */ | ||
+ | class Wyrazenie : boost::noncopyable | ||
+ | { | ||
+ | public: | ||
+ | |||
+ | /** Zwraca wartosc kolejnego, znajdujacego sie na poczatku ciagu s, | ||
+ | * wyrazenia, w zaleznosci od aktualnego kontekstu. | ||
+ | * Usuwa wykorzystane w aktualnym kroku cyfry z ciagu. | ||
+ | */ | ||
+ | int interpretuj( std::string & s ); | ||
+ | |||
+ | /** Zwracaja symbole cyfr dla konkretnego - zaleznego od konkretnej | ||
+ | * klasy pochodnej - kontekstu (miejsca w liczbie). | ||
+ | * Mamy cztery rodzaje cyfr w systemie rzymskim: jeden, cztery, | ||
+ | * piec i dziewiec. Odpowiedniki pozostalych cyfr arabskich sa | ||
+ | * tworzone przez sumowanie w/w cyfr. | ||
+ | * W systemie dziesietnym znaki odpowiadajace cyfrom sa zawsze takie | ||
+ | * same, "1" jest taka sama dla jednosci, dziesiatek, setek itd. | ||
+ | * Natomiast w systemie rzymskim "1" rozni sie w zaleznosci | ||
+ | * od kontekstu: "I" dla jednosci, "X" dla dziesiatek itd. | ||
+ | */ | ||
+ | virtual std::string jeden() = 0; | ||
+ | virtual std::string cztery() = 0; | ||
+ | virtual std::string piec() = 0; | ||
+ | virtual std::string dziewiec() = 0; | ||
+ | |||
+ | /** Mnoznik, przez ktory nalezy pomnozyc wynikowa cyfre w danym | ||
+ | * kontekscie. Analogia do mnozenia kolejnych cyfr liczby dziesietnej | ||
+ | * przez kolejne potegi 10. | ||
+ | */ | ||
+ | virtual int mnoznik() = 0; | ||
+ | }; | ||
+ | |||
+ | /** Oblicza wartosc wszystkich cyfr w danym kontekscie (miejscu w liczbie), | ||
+ | * czyli np. dla wszystkich cyfr skladajacych sie na setki | ||
+ | * i usuwa wykorzystane cyfry z ciagu. | ||
+ | */ | ||
+ | int Wyrazenie::interpretuj( std::string & s ) | ||
+ | { | ||
+ | /** Sprawdza, czy ciag wejsciowy zaczyna sie od podanego podciagu. | ||
+ | */ | ||
+ | using boost::algorithm::istarts_with; | ||
+ | |||
+ | if ( s.length() == 0 ) | ||
+ | return 0; | ||
+ | |||
+ | /** Suma cyfr dla danego kontekstu */ | ||
+ | int suma = 0; | ||
+ | |||
+ | /* Mamy dwa rodzaje cyfr podwojnych - dziewiec i cztery. | ||
+ | * W jednym kontekscie zadne inne cyfry nie moga wystapic z | ||
+ | * nimi rownoczesnie. | ||
+ | */ | ||
+ | if ( istarts_with( s, dziewiec() ) ) | ||
+ | |||
+ | { | ||
+ | /* Usun dwa pierwsze znaki cyfry podwojnej z ciagu */ | ||
+ | s.erase( 0, 2 ); | ||
+ | suma += 9 * mnoznik(); | ||
+ | /* Po dziewiatce (i podobnie po czworce) nie moga wystapic | ||
+ | * inne cyfry w jednym kontekscie, wiec mozemy wyjsc od razu | ||
+ | */ | ||
+ | return suma; | ||
+ | } | ||
+ | else if ( istarts_with( s, cztery() ) ) | ||
+ | { | ||
+ | s.erase( 0, 2 ); | ||
+ | suma += 4 * mnoznik(); | ||
+ | return suma; | ||
+ | } | ||
+ | else if ( istarts_with( s, piec() ) ) | ||
+ | { | ||
+ | s.erase( 0, 1 ); | ||
+ | suma += 5 * mnoznik(); | ||
+ | /* Po piatce natomiast moga wystapic jedynki, wiec | ||
+ | * nie mozemy wyjsc przedwczesnie */ | ||
+ | } | ||
+ | |||
+ | |||
+ | /* Jedynki jako jedyne moga wystapic wielokrotnie po sobie | ||
+ | * w jednym kontekscie, np. dla "III" zostanie | ||
+ | * trzykrotnie usuniety symbol jedynki i trykrotnie do sumy dodane | ||
+ | * zostanie 1 * mnoznik, czyli po prostu mnoznik. | ||
+ | */ | ||
+ | while ( istarts_with( s, jeden() ) ) | ||
+ | { | ||
+ | s.erase( 0, 1 ); | ||
+ | suma += mnoznik(); | ||
+ | } | ||
+ | |||
+ | return suma; | ||
+ | } | ||
+ | |||
+ | /** Konkretne klasy dla danego kontekstu. | ||
+ | * Zwracaja symbole cyfr o konkretnym znaczeniu w danym kontekscie, | ||
+ | * na przyklad "czworka" dla jednosci jest "IV", a dla setek "CD". | ||
+ | */ | ||
+ | class Tysiace : public Wyrazenie | ||
+ | { | ||
+ | public: | ||
+ | |||
+ | std::string jeden() { return "M"; } | ||
+ | std::string cztery() { return " "; } | ||
+ | std::string piec() { return " "; } | ||
+ | std::string dziewiec() { return " "; } | ||
+ | |||
+ | int mnoznik() { return 1000; } | ||
+ | }; | ||
+ | |||
+ | class Setki : public Wyrazenie | ||
+ | { | ||
+ | public: | ||
+ | |||
+ | std::string jeden() { return "C"; } | ||
+ | std::string cztery() { return "CD"; } | ||
+ | std::string piec() { return "D"; } | ||
+ | std::string dziewiec() { return "CM"; } | ||
+ | | ||
+ | int mnoznik() { return 100; } | ||
+ | }; | ||
+ | |||
+ | class Dziesiatki : public Wyrazenie | ||
+ | { | ||
+ | public: | ||
+ | |||
+ | std::string jeden() { return "X"; } | ||
+ | std::string cztery() { return "XL"; } | ||
+ | std::string piec() { return "L"; } | ||
+ | std::string dziewiec() { return "XC"; } | ||
+ | | ||
+ | int mnoznik() { return 10; } | ||
+ | }; | ||
+ | |||
+ | class Jednosci : public Wyrazenie | ||
+ | { | ||
+ | public: | ||
+ | |||
+ | std::string jeden() { return "I"; } | ||
+ | std::string cztery() { return "IV"; } | ||
+ | std::string piec() { return "V"; } | ||
+ | std::string dziewiec() { return "IX"; } | ||
+ | | ||
+ | int mnoznik() { return 1; } | ||
+ | }; | ||
+ | |||
+ | int main() | ||
+ | { | ||
+ | /* 2938 */ | ||
+ | std::string rzymska( "MMCMXXXVIII" ); | ||
+ | |||
+ | /* Stworzenie gramatyki liczb rzymskich, w ktorej wystepuja, | ||
+ | * w kolejnosci, cyfry skladajace sie na: tysiace, setki, dziesiatki | ||
+ | * i jednosci. W ten sposob kazda sekwencja znakow jest inaczej | ||
+ | * interpretowana, w zaleznosci do tego, na jakim miejscu znajduje | ||
+ | * sie w ciagu. | ||
+ | * Tworzenie listy ma tylko na celu ulatwienie dalszego korzystania. | ||
+ | * Ponadto jest to tylko jedna z mozliwosci, czesto korzysta sie z gramatyk | ||
+ | * opartych o struktury drzewiaste. | ||
+ | */ | ||
+ | boost::ptr_list< Wyrazenie > wyrazenia; | ||
+ | wyrazenia.push_back( new Tysiace() ); | ||
+ | wyrazenia.push_back( new Setki() ); | ||
+ | wyrazenia.push_back( new Dziesiatki() ); | ||
+ | wyrazenia.push_back( new Jednosci() ); | ||
+ | |||
+ | int suma = 0; | ||
+ | |||
+ | /* Przeanalizuj cale wyrazenie dla kolejnych kontekstow zgodnie z gramatyka. | ||
+ | * Kolejno dla kazdego kontekstu przeanalizuj poczatek ciagu i usun | ||
+ | * wykorzystane cyfry, dodajac do sumy aktualnie obliczane wyrazenia. Ciag | ||
+ | * jest przetwarzany zgodnie z wczesniej ustalonym porzadkiem przy tworzeniu | ||
+ | * listy (gramatyki) wyrazen, poczawszy od tysiecy po jednosci. | ||
+ | */ | ||
+ | BOOST_FOREACH( boost::ptr_list< Wyrazenie >::reference w, wyrazenia ) | ||
+ | { | ||
+ | /* Wywoluje interpretuj() dla konkretnego potomka klasy Wyrazenie | ||
+ | * (dziala mechanizm funkcji wirtualnych) | ||
+ | */ | ||
+ | suma += w.interpretuj( rzymska ); | ||
+ | } | ||
+ | |||
+ | assert( suma == 2938 ); | ||
+ | std::cout << suma << std::endl; | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </code> |