
//
// To jest przykładowy kod z podrozdziału 7.4 "Liczby ujemne" 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 liczby typu double.
        :kind(ch), value(val) { }
};

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

class Token_stream {
public: 
    Token_stream();   // Tworzy strumień tokenów wczytujący dane ze strumienia cin.
    Token get();      // Pobiera token (funkcja get() została zdefiniowana w innym miejscu).
    void putback(Token t); // Zwraca token.
private:
    bool full;        // Sprawdza czy w buforze jest token.
    Token buffer;     // Przechowuje tokeny zwrócone przez funkcję putback().
};

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

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

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

// Funkcja składowa putback() wstawia swój argument z powrotem do bufora Token_stream::
void Token_stream::putback(Token t)
{
    if (full) error("Funkcja putback() wstawia dane 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 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;              // Wczytuje liczbę zmiennoprzecinkową.
            return Token('8',val);   // Znak 8 reprezentuje liczbę.
        }
    default:
        error("Bad token");
    }
}

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

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

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

double expression();    // Dzięki tej deklaracji 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':                  // Znak 8 reprezentuje liczbę.
        return t.value;        // Zwraca wartość liczby.
    case '-':
        return - primary();
    case '+':
        return primary();
    default:
        error("Oczekiwano czynnika.");
    }
}

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

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

    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 tokenów.

    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
{
    while (cin) {
        cout << "> ";
        Token t = ts.get();
        while (t.kind == ';') t=ts.get();    // zjada ';'
        if (t.kind == 'q') {
            keep_window_open();
            return 0;
        }
        ts.putback(t);
        cout << "= " << expression() << endl;
    }
    keep_window_open();
    return 0;
}
catch (exception& e) {
    cerr << e.what() << endl;
    keep_window_open("~~");
    return 1;
}
catch (...) {
    cerr << "Wyjątek \n";
    keep_window_open("~~");
    return 2;
}

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