/* ________________________________________________________________________________________________________

	rpn100.cpp      A Tiny RPN Calculator (;-}=   v1.00

	Author: Gordon M. Brown      Revised 2009.07.30

	XGB Web and Software Design      www.xgbdesign.com

	First draft of an object-oriented implementation of the program presented in "The C Programming	
	Language" by Kernighan and Ritchie (2nd ed.), pp. 76-79; and "The C Answer Book" by Tondo and 
	Gimpel, pp. 73-92. Aside from organizing the code more intelligibly, other major objectives 
	of using C++ include eliminating all global variables, and exploiting the power of the string 
	class included in the ANSI/ISO C++ standard, so that we can do away with all such functions as 
	getch(), ungetch(), getline(), getop(), etc. For additional documentation of this source file,
	visit http://www.xgbdesign.com/c++code/rpn-calculator/version1-00.html.
   ________________________________________________________________________________________________________

	PLEASE NOTE: This is an HTML webpage. To access this source code, simply select and copy the
	text on this page, and paste it into your favorite text editor or IDE. Please DO NOT use the
	underlying HTML source code, as it contains certain HTML character codes that will not compile
	in C++.

	Valid XHTML 1.0 Strict    Valid CSS 2.1
   ________________________________________________________________________________________________________
*/ 

#include <iostream>
#include <cmath>
using namespace std;

// Class declarations: ____________________________________________________________________________

class Stack {
public:
	Stack();
	void Clear();
	void Push(double);
	double Pop();

private:
	// Fields:
	int freeStackPosition;
	double elements[100];
};


class Calculator {
public:
	Calculator();
	void Run();

private:
	// Fields:
	double variables[26];   // Variables A through Z
	string entry;           // Stores current entry
	string lastEntry;       // For temporary storage of a possible variable name
	Stack stack;            // Implements RPN calculation

	// Methods:
	void PushNumericValue();
	void PushVariableValue();
	void DeployOperator();
	void DeployMathFunction();
	void DeployStackFunction();
	bool EntryIsNumeric();
	bool EntryIsOperatorName();
	bool EntryIsVariableName();
	bool LastEntryIsVariableName();
	bool EntryIsStackFunctionName(); 
};


// Stack: Constructor and public methods: _________________________________________________________

Stack::Stack() { 
	freeStackPosition = 0; 
}

void Stack::Clear() { 
	freeStackPosition = 0; 
}

void Stack::Push(double val) {
	if (freeStackPosition < 100)
		elements[freeStackPosition++] = val;
	else
		cout << "   ERROR: Stack full; can't push " << val << ".\n";
}

double Stack::Pop() {
	if (freeStackPosition > 0)
		return elements[--freeStackPosition];
	else {
		cout << "   ERROR: Stack empty.\n";
		return 0.0;
	}
}


// Calculator: Constructor and public methods: ____________________________________________________

Calculator::Calculator() {
	for (int i = 0; i < 26; ++i)
		variables[i] = 0.0;
	entry = "";
	lastEntry = "";
	cout.precision(12);
	cout << "\nA Tiny RPN Calculator (;-}=   v1.00\n\n";
}

void Calculator::Run() {
	while (cin >> entry) {
		if (EntryIsNumeric())
			PushNumericValue();
		else if (EntryIsVariableName())
			PushVariableValue();
		else if (EntryIsOperatorName())
			DeployOperator();
		else if (EntryIsStackFunctionName())
			DeployStackFunction();
		else
			DeployMathFunction();
		lastEntry = entry;
	}
}


// Calculator: Private methods: ___________________________________________________________________

void Calculator::PushNumericValue() {
	stack.Push(atof(entry.c_str()));
}

void Calculator::PushVariableValue() {
	stack.Push(variables[entry[0] - 'A']);
}

void Calculator::DeployOperator() {
	double arg1;
	switch (entry[0]) {
		case '+':
			stack.Push(stack.Pop() + stack.Pop());
			break;
		case '*':
			stack.Push(stack.Pop() * stack.Pop());
			break;
		case '-':
			arg1 = stack.Pop();
			stack.Push(stack.Pop() - arg1);
			break;
		case '/':
			arg1 = stack.Pop();
			if (arg1 != 0.0)
				stack.Push(stack.Pop() / arg1);
			else
				cout << "   ERROR: Attempted division by zero.\n";
			break;
		case '%':
			arg1 = stack.Pop();
			if (arg1 != 0.0)
				stack.Push(fmod(stack.Pop(), arg1));
			else
				cout << "   ERROR: Attempted division by zero.\n";
			break;
		case '=':
			stack.Pop();   // Pop previously assigned (or spuriously assigned) variable value first
			if (LastEntryIsVariableName())
				variables[lastEntry[0] - 'A'] = stack.Pop();
			else
				cout << "   ERROR: No variable being assigned to.\n";
			break;
	}
}
	
void Calculator::DeployStackFunction() {
	double arg1, arg2;
	if (entry == "?") {           // If querying, or preparing program output,...
		arg1 = stack.Pop();   // ...output top element in stack
		stack.Push(arg1);
		cout << "   " << arg1 << endl;
	} 
	else if (entry == "dp") {		
		arg1 = stack.Pop();   // Duplicate top element
		stack.Push(arg1);
		stack.Push(arg1);					
	}
	else if (entry == "sw") {
		arg1 = stack.Pop();   // Swap top two elements
		arg2 = stack.Pop();
		stack.Push(arg1);
		stack.Push(arg2);					
	}
	else if (entry == "cl")
		stack.Clear();        // Clear the stack
}

void Calculator::DeployMathFunction() {
	double arg1;
	const double pi = 3.141592653589793;

	// Push some constants onto the stack (N.B.: The Golden Mean and physical
	// constants are included simply to demonstrate the calculator's potential.
	// In future drafts we'll have ways to eliminate hard-coding of such constants):
	if (entry == "pi")
		stack.Push(pi);
	else if (entry == "e")
		stack.Push(2.718281828459045);
	else if (entry == "gm")
		stack.Push(1.618033988749895);  // Golden Mean
	else if (entry == "au")
		stack.Push(1.495e+11);          // Astronomical unit (meters)
	else if (entry == "c")
		stack.Push(299792458);          // Speed of light in a vacuum (meters/second)
	else if (entry == "yr")
		stack.Push(31556926);           // Year (seconds)
	else if (entry == "ly")
		stack.Push(9.46053e+15);        // Light-year (meters)

	// Trigonometric functions:
	else if (entry == "sin")
		stack.Push(sin(stack.Pop()));
	else if (entry == "cos")
		stack.Push(cos(stack.Pop()));
	else if (entry == "tan")
		stack.Push(tan(stack.Pop()));
	else if (entry == "asin")
		stack.Push(asin(stack.Pop()));
	else if (entry == "acos")
		stack.Push(acos(stack.Pop()));
	else if (entry == "atan")
		stack.Push(atan(stack.Pop()));
	else if (entry == "deg")                          // Converts radians to degrees
		stack.Push(stack.Pop() * 180.0 / pi);
	else if (entry == "rad")                          // Converts degrees to radians
		stack.Push(stack.Pop() * pi / 180.0);

	// Hyperbolic functions:
	else if (entry == "sinh")
		stack.Push(sinh(stack.Pop()));
	else if (entry == "cosh")
		stack.Push(cosh(stack.Pop()));
	else if (entry == "tanh")
		stack.Push(tanh(stack.Pop()));

	// Exponential and logarithmic functions:
	else if (entry == "exp")                          // Natural exponent ("e to the x")
		stack.Push(exp(stack.Pop()));
	else if (entry == "ln")                           // Natural log (base e)
		stack.Push(log(stack.Pop()));
	else if (entry == "log")                          // Common log (base 10)
		stack.Push(log10(stack.Pop()));
	else if (entry == "sqrt")                         // Square root
		stack.Push(sqrt(stack.Pop()));
	else if (entry == "sq")                           // Square
		stack.Push(pow(stack.Pop(), 2.0));
	else if (entry == "cbrt")                         // Cube root
		stack.Push(pow(stack.Pop(), 1.0 / 3.0));
	else if (entry == "cb")                           // Cube
		stack.Push(pow(stack.Pop(), 3.0));

	// Functions for formatting numbers:
	else if (entry == "pr") {
		int prec = stack.Pop();                   // Resets output precision on the fly
		if (prec >= 0 && prec <= 16)
			cout.precision(prec);
		else
			cout << "   ERROR: " << prec << " not a valid setting for precision (0 <= precision <= 16 is valid).\n";
	}
	else if (entry == "ceil")                         // Rounds up
		stack.Push(ceil(stack.Pop()));
	else if (entry == "floor")                        // Rounds down
		stack.Push(floor(stack.Pop()));
	else if (entry == "round")                        // Rounds to nearest whole number
		stack.Push(floor(stack.Pop() + 0.5));

	// Other useful math functions:
	else if (entry == "r") {                          // Reciprocation
		arg1 = stack.Pop();
		if (arg1 != 0.0)
			stack.Push(1.0 / arg1);
		else
			cout << "   ERROR: Attempted division by zero.\n";
	}
	else if (entry == "chs")                          // Change sign
		stack.Push(-(stack.Pop()));
	else if (entry == "abs")                          // Absolute value
		stack.Push(fabs(stack.Pop()));
	else if (entry == "yx") {                         // The power function ("y to the x")
		arg1 = stack.Pop();
		stack.Push(pow(stack.Pop(), arg1));
	}

	// Otherwise, it's not supported:
	else
		cout << "   ERROR: \"" << entry << "\" is not a supported function.\n";
}

bool Calculator::EntryIsNumeric() {
	return (isdigit(entry[0])) ||
		(entry[0] == '.' && isdigit(entry[1])) ||
		(entry[0] == '-' && isdigit(entry[1])) ||
		(entry[0] == '-' && entry[1] == '.' && isdigit(entry[2]));
}

bool Calculator::EntryIsVariableName() { 
	return entry.length() == 1 && entry >= "A" && entry <= "Z"; 
}

bool Calculator::LastEntryIsVariableName() {
	return lastEntry.length() == 1 && lastEntry >= "A" && lastEntry <= "Z"; 
}

bool Calculator::EntryIsOperatorName() { 
	if (entry.length() == 1) {
		string operators = "+-*/%=";
		for (int i = 0; i < 6; ++i)
			if (entry[0] == operators[i])
				return true;
	}
	return false;
}

bool Calculator::EntryIsStackFunctionName() { 
	return entry == "?" || entry == "dp" || entry == "sw" || entry == "cl"; 
}


// The main() function: ___________________________________________________________________________

int main() {
	Calculator tinyCalculator;
	tinyCalculator.Run();
	return 0;
}