
///
/// This is example code from Chapter 6.7 "Trying the second version" of
/// "Software - Principles and Practice using C++" by Bjarne Stroustrup
///

/**
	This version incorporates exponentials and factorials and handles variables

*/

#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 BY 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 ! or ^ (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;
}
}

//------------------------------------------------------------------------------
