/* ________________________________________________________________________________________________________

	rpn101.cpp      A Tiny RPN Calculator (;-}=   v1.01

	Author: Gordon M. Brown      Revised 2009.07.31

	XGB Web and Software Design      www.xgbdesign.com

	Version 1.01 extends the capabilities of Version 1.00 by allowing the user to create and invoke 
	exactly one programmable sequence of commands. To this end the class Program is introduced, and 
	an instance of Program is made a member of the Calculator class. For additional documentation of
	this source file, visit http://www.xgbdesign.com/c++code/rpn-calculator/version1-01.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>
#include <vector>   // New in v1.01
using namespace std;

// Class declarations: ____________________________________________________________________________

// New in v1.01:
class Program {
public:
	Program();
	void CheckOperation(string&);
	bool IsRunning();

private:
	// Fields:
	bool isRunning;
	int count;
	vector<string> instructions;

	// Methods:
	void Set();
	string NextInstruction();
};


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
	Program program;        // Allows user to create and run one program   New in v1.01

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


// Program: Constructor and public methods: _______________________________________________________

Program::Program() {
	isRunning = false; 
	count = 0; 
}

void Program::CheckOperation(string& entry) {
	if (entry == "pgm")
		Set();
	if (entry == "run") {
		isRunning = true;
		count = 0;
	}
	if (isRunning)
		entry = NextInstruction();
}

bool Program::IsRunning() {
	return isRunning;
}


// Program: Private methods: ______________________________________________________________________

void Program::Set() {
	// Flush the vector first:
	instructions.resize(0);
	string newEntry = "";
	cout << "Enter a sequence of program instructions; terminate by entering \"end\": ";
	while (cin >> newEntry && newEntry != "end")
		instructions.push_back(newEntry);
	cout << "   Program created.\n";
}

string Program::NextInstruction() {
	string next = instructions[count++];
	if (count == instructions.size())
		isRunning = false;
	return next;
}


// 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.01\n\n";
}

void Calculator::Run() {
	// I.e., while the program is running, or user enters instructions manually...
	while (program.IsRunning() || cin >> entry) {   // New in v1.01
		program.CheckOperation(entry);
		if (entry == "pgm")     // New in v1.01; Traps the instruction "pgm" so that
			;               // it's not claimed to be a nonexistent math function 
		else 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;
}