Narzędzia użytkownika

Narzędzia witryny


interpreter

Różnice

Różnice między wybraną wersją a wersją aktualną.

Odnośnik do tego porównania

Both sides previous revision Previous revision
interpreter [2008/12/07 11:19]
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 ​WyrażenieLiczbapochodne 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 54: Linia 54:
  
 ===== 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>​
interpreter.txt · ostatnio zmienione: 2008/12/07 11:33 przez posciak