
//
// Przykładowy kod z rozdziału 6.7 "Wypróbowywanie drugiej wersji" książki
// "Programowanie. Teoria i praktyka z wykorzystaniem C++" Bjarne'a Stroustrupa.
//

#include "std_lib_facilities.h"

//------------------------------------------------------------------------------

class Token {
public:
    char kind;        // Rodzaj tokenu.
    double value;     // Dla liczb: wartość.
    Token(char ch)    // Tworzy token ze znaku.
        :kind(ch), value(0) { }    
    Token(char ch, double val)     // Tworzy Token ze znaku i wartości typu double.
        :kind(ch), value(val) { }
};

//------------------------------------------------------------------------------

class Token_stream {
public: 
    Token_stream();   // Tworzy Token_stream wczytujący ze strumienia cin.
    Token get();      // Pobiera token (funkcja get() jest zdefiniowana w innym miejscu).
    void putback(Token t);    // Wstawia token z powrotem.
private:
    bool full;        // Sprawdza czy w buforze jest token.
    Token buffer;     // Przechowuje token wstawiony z powrotem za pomocą funkcji putback().
};

//------------------------------------------------------------------------------

// Konstruktor ustawia tylko wartość full w taki sposób, aby wskazywała, że bufor jest pusty:
Token_stream::Token_stream()
:full(false), buffer(0)    // Bufor jest pusty
{
}

//------------------------------------------------------------------------------

// Funkcja składowa putback() wstawia swój argument z powrotem do bufora strumienia Token_stream:
void Token_stream::putback(Token t)
{
    if (full) error("Funkcja putback() próbuje wstawić token do pełnego bufora.");
    buffer = t;       // Kopiuje t do bufora.
    full = true;      // Bufor jest pełny.
}

//------------------------------------------------------------------------------

Token Token_stream::get()
{
    if (full) {       // Sprawdza czy jest już gotowy token.
        // Usuwa token z bufora.
        full=false;
        return buffer;
    } 

    char ch;
    cin >> ch;    // Operator >> pomija białe znaki (spacje, znaki nowego wiersza, tabulatory itp.).

    switch (ch) {
    case ';':    // Oznacza drukowanie
    case 'q':    // Oznacza koniec
    case '(': case ')': case '+': case '-': case '*': case '/': 
        return Token(ch);        // Każdy znak reprezentuje sam siebie.
    case '.':
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
        {    
            cin.putback(ch);         // Wstawia cyfrę z powrotem do strumienia wejściowego.
            double val;
            cin >> val;              // Odczytuje liczbę zmiennoprzecinkową.
            return Token('8',val);   // Znak 8 reprezentuje liczbę.
        }
    default:
        error("Nieprawidłowy token");
    }
}

//------------------------------------------------------------------------------

Token_stream ts;        // Dostarcza funkcji get() i putback().

//------------------------------------------------------------------------------

double expression();    // Deklaracja, dzięki której funkcja primary() może wywołać funkcję expression().

//------------------------------------------------------------------------------

// Obsługa liczb i nawiasów.
double primary()
{
    Token t = ts.get();
    switch (t.kind) {
    case '(':    // Obsługa '(' wyrażenie ')'
        {    
            double d = expression();
            t = ts.get();
            if (t.kind != ')') error("')' był oczekiwany");
            return d;
        }
    case '8':            // Do reprezentowania liczb służy znak 8.
        return t.value;  // Zwraca wartość liczby.
    default:
        error("Oczekiwano czynnika.");
    }
}

//------------------------------------------------------------------------------

// Obsługa *, / i %.
double term()
{
    double left = primary();
    Token t = ts.get();        // Pobiera następny token ze strumienia wejściowego.

    while(true) {
        switch (t.kind) {
        case '*':
            left *= primary();
            t = ts.get();
            break;
        case '/':
            {    
                double d = primary();
                if (d == 0) error("Dzielenie przez zero.");
                left /= d; 
                t = ts.get();
                break;
            }
        default: 
            ts.putback(t);     // Wstawia t z powrotem do strumienia tokenów.
            return left;
        }
    }
}

//------------------------------------------------------------------------------

// Obsługa + i -.
double expression()
{
    double left = term();      // Wczytuje i oblicza wartość składnika.
    Token t = ts.get();        // Pobiera następny token ze strumienia wejściowego.

    while(true) {    
        switch(t.kind) {
        case '+':
            left += term();    // Oblicza wartość składnika i dodaje.
            t = ts.get();
            break;
        case '-':
            left -= term();    // Oblicza wartość składnika i odejmuje.
            t = ts.get();
            break;
        default: 
            ts.putback(t);     // Wstawia t z powrotem do strumienia tokenów.
            return left;       // Nie ma więcej znaków = i -. Zwraca wynik.
        }
    }
}

//------------------------------------------------------------------------------

int main()
try
{
    double val = 0;
    while (cin) {
        Token t = ts.get();

        if (t.kind == 'q') break; // Oznacza koniec.
        if (t.kind == ';')        // Oznacza drukowanie.
            cout << "=" << val << '\n';
        else
            ts.putback(t);
        val = expression();
    }
}
catch (exception& e) {
    cerr << "Błąd: " << e.what() << '\n'; 
    return 1;
}
catch (...) {
    cerr << "Nieznany wyjątek!\n"; 
    return 2;
}

//------------------------------------------------------------------------------
