
///
/// This is example code from Chapter 6.7 "Trying the second version" of
/// "Software - Principles and Practice using C++" by Bjarne Stroustrup
///

/**
	This file is known as calculator02buggy.cpp

	I have inserted 5 errors that should cause this not to compile
	I have inserted 3 logic errors that should cause the program to give wrong results

	First try to find an remove the bugs without looking in the book.
	If that gets tedious, compare the code to that in the book (or posted source code)

	Happy hunting!

*/

#include "std_lib_facilities.h"

///------------------------------------------------------------------------------
///Globals

const string prompt = "> ";
const char number = '8';
const char quit = 'q';
const char print = ';';
const char name = 'a'; /// name token
const char let = 'L'; /// declaration token
const string declkey = "let"; /// declaration keyword

///------------------------------------------------------------------------------

class Token {
public:
    char kind;        /// what kind of token
    double value;     /// for numbers: a value
    string name;      /// for variables: a string
    Token(char ch)    /// make a Token from a char
        :kind(ch), value(0) { }
    Token(char ch, double val)     /// make a Token from a char and a double
        :kind(ch), value(val) { }
    Token(char ch, string s)        /// make a Token from a char and a string
        :kind(ch), name(s) { }
};

///------------------------------------------------------------------------------

class Token_stream {
public:
    Token_stream();   /// make a Token_stream that reads from cin
    Token get();      /// get a Token (get() is defined elsewhere)
    void putback(Token t);    /// put a Token back
    void ignore(char c);
private:
    bool full;        /// is there a Token in the buffer?
    Token buffer;     /// here is where we keep a Token put back using putback()
};

//------------------------------------------------------------------------------

/// The constructor just sets full to indicate that the buffer is empty:
Token_stream::Token_stream()
:full(false), buffer(0)    /// no Token in buffer
{ }

///------------------------------------------------------------------------------

/// The putback() member function puts its argument back into the Token_stream's buffer:
void Token_stream::putback(Token t)
{
    if (full) error("putback() into a full buffer");
    buffer = t;       // copy t to buffer
    full = true;      // buffer is now full
}

///------------------------------------------------------------------------------

Token Token_stream::get()
{
    if (full) {       /// do we already have a Token ready?
        /// remove token from buffer
        full=false;
        return buffer;
    }
    char ch;
    cin >> ch;    /// note that >> skips whitespace (space, newline, tab, etc.)

    switch (ch) {
    case print:   /// for "print"case ';':
    case quit:
    case '(': case ')': case '+': case '-':
    case '*': case '/': case '!': case '^':
    case '%': case 'a': case 'L': case '=':
        return Token(ch);        // let each character represent itself
    case '.':
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
    {
        cin.putback(ch);         /// put digit back into the input stream
        double val;
        cin >> val;              /// read a floating-point number
        return Token(number,val);   /// let '8' represent "a number"
    }
    default:
        if (isalpha(ch)) {
            string s;
            s += ch;
            while (cin.get(ch) && (isalpha(ch) || isdigit(ch)))
                s+=ch;
            cin.putback(ch);
            if (s == declkey) return Token(let); /// declaration keyword
            return Token{name,s};
        }
        error("Bad token");
    }
}

///------------------------------------------------------------------------------

void Token_stream::ignore(char c) {
    /// first look in buffer:
    if (full && c==buffer.kind) {
        full = false;
        return;
    }
    full = false;

    char next = 0;
    while(cin >> next) {
        if(next == c) return;
    }
}

///------------------------------------------------------------------------------

///Token_stream ts;        /// provides get() and putback()

class Variable {
public:
    string name;
    double value;
    Variable(string s, double d) : name(s), value(d) {}
};

vector<Variable> var_table;

double get_value(string s)
/// return the value of the Variable named s
{
    for (const Variable& v : var_table)
        if (v.name == s) return v.value;
    error("get: undefined variable ", s);
}

void set_value(string s, double d)
/// set the Variable named s to d
{
    for (Variable& v : var_table)
        if (v.name == s) {
            v.value = d;
            return;
        }
    error("set: undefined variable ", s);
}

bool is_declared(string var) {
/// is var already in var_table?
    for (const Variable& v : var_table)
        if (v.name == var) return true;
    return false;
}

double define_name(string var, double val) {
/// add (var,val) to var_table
    if (is_declared(var)) error(var," declared twice");
    var_table.push_back(Variable(var,val));
    return val;
}
///------------------------------------------------------------------------------

double expression(Token_stream&);    /// declaration so that primary() can call expression()

///------------------------------------------------------------------------------

/// deal with numbers and parentheses (IS CALLED FACTORIAL)
double primary(Token_stream& ts)
{
    Token t = ts.get();
    switch (t.kind) {
    case '(':    /// handle '(' expression ')'
    {
        double d = expression(ts);
        t = ts.get();
        if (t.kind != ')') error("')' expected");
        return d;
    }
    case number :            /// we use '8' to represent a number
        return t.value;  /// return the number's value
    case '-':
        return -primary(ts);
    ///case '+':
       /// return primary();
    default:
        error("primary expected");
    }
}

//------------------------------------------------------------------------------
///------------------------------------------------------------------------------
/// deal with ! (IS CALLED BY TERM)
double factorial(Token_stream& ts) {
    double left = primary(ts);
    Token t = ts.get();
    if(t.kind == '!') {
        if(left-int(left)!=0) error("can't factorial a non-integer");
        if(left < 0) error("can't factorial a negative");
        if(left==0) return 1.0;
        double accumulator=left;
        --left;
        while(left>0) {
            accumulator *= left;
            --left;
        }
        return accumulator;
    }
    else if(t.kind == '^') {
        double exponent = primary(ts);
        if(exponent-int(exponent)!=0) error("We can't raise to a non-integer exponent.");
        if(exponent==0) return 1.0;
        double accumulator=1;
        if(exponent>0) {
            for(int i = exponent; i > 0; --i)
                accumulator *= left;
            return accumulator;
        }
        else {
            for(int i = exponent; i < 0; ++i)
                accumulator /= left;
            return accumulator;
        }
    }
    else {
        ts.putback(t);
        return left;
    }
}
///------------------------------------------------------------------------------

/// deal with *, and  / (IS CALLED BY EXPRESSION)
double term(Token_stream& ts)
{
    double left = factorial(ts); ///primary();
    Token t = ts.get();        // get the next token from token stream

    while(true) {
        switch (t.kind) {
        case '*':
            left *= factorial(ts); ///primary();
            t = ts.get();
            break;
        case '/':
        {
            double d = factorial(ts); ///primary();
            if (d == 0) error("divide by zero");
            left /= d;
            t = ts.get();
            break;
        }
        case '%':
        {
            double d = factorial(ts); ///primary();
            if (d == 0) error("divide by zero");
            left = fmod(left,d);
            /*if(left - int(left)!=0 || d - int(d)!=0)
                error("modulo operands must be positive integers.");
            int temp = int(left);
            temp %= d;
            left = double(temp);*/
            t = ts.get();
            break;
        }
        default:
            ts.putback(t);     /// put t back into the token stream
            return left;
        }
    }
}


///------------------------------------------------------------------------------

/// deal with + and -
double expression(Token_stream& ts)
{
    double left = term(ts);      /// read and evaluate a Term
    Token t = ts.get();        /// get the next token from token stream

    while(true) {
        switch(t.kind) {
        case '+':
            left += term(ts);    // evaluate Term and add
            t = ts.get();
            break;
        case '-':
            left -= term(ts);    // evaluate Term and subtract
            t = ts.get();
            break;
        default:
            ts.putback(t);     // put t back into the token stream
            return left;       // finally: no more + or -: return the answer
        }
    }
}

///------------------------------------------------------------------------------

void clean_up_mess(Token_stream& ts) {  ///naive
    ts.ignore(print);
}

///------------------------------------------------------------------------------

double declaration(Token_stream& ts)
/// assume we have seen "let”
/// handle: name = expression
/// declare a variable called "name” with the initial value "expression”
{
    Token t = ts.get();
    if (t.kind != name) error ("name expected in declaration");
    string var_name = t.name;

    Token t2 = ts.get();
    if (t2.kind != '=') error("= missing in declaration of ", var_name);

    double d = expression(ts);
    define_name(var_name,d);
    return d;  ///not crucial, but could be useful
}

///------------------------------------------------------------------------------

double statement(Token_stream& ts) {
    Token t = ts.get();
    switch (t.kind) {
    case let:
        return declaration(ts);
    default:
        ts.putback(t);
        return expression(ts);
    }
}
void calculate() {
    Token_stream ts;
    while (cin)
    try
    {
        cout << prompt;
        Token t = ts.get();
        while(t.kind == print) t=ts.get();
        if (t.kind == quit) return;
        ts.putback(t);
        cout << "=" << statement(ts) << '\n';
    }
    catch (exception& e) {
        cerr << e.what() << '\n';
        clean_up_mess(ts);
    }
}
///------------------------------------------------------------------------------

int main() {
try {
    cout << "\nWelcome to our simple calculator."
         << "\nPlease enter expressions using floating-point numbers."
         << "\nYou can use +, - , *, / ! or ^ (note problems with ! & ^):\n";
    /// predefine names:
    define_name("pi",3.1415926535);
    define_name("e",2.7182818284);
    calculate();
}
catch (...) {
        cerr << "Oops: unknown exception!\n";
        return 1;
}
}

//------------------------------------------------------------------------------
