/* ________________________________________________________________________________________________________

	rpn103.cpp      A Tiny RPN Calculator (;-}=   v1.03

	Author: Gordon M. Brown      Revised 2009.08.03

	XGB Web and Software Design      www.xgbdesign.com

	Version 1.03 enables users to embed program names within programs. Now a program can call another
	program nested inside of it. This nesting can take place for up to 24 levels. Version 1.03 also
	validates prospective names of programs, rejecting numeric literals, and names of variables, 
	operators, and dedicated math functions. For additional documentation of this source file, visit
	http://www.xgbdesign.com/c++code/rpn-calculator/version1-03.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>
using namespace std;

// Class declarations: ____________________________________________________________________________

class Program {
public:
	Program();
	Program(string);

	// Mutator methods:
	void ResetCounter();
	void Flush();

	// Read-only accessor methods:
	bool IsFinished();
	string Name();
 	string NextInstruction();

private:
	// Fields:
	int count;
	string name;
	vector<string> instructions;
};


// Revised for v1.03:
class ProgramManager {
public:
	ProgramManager();                      // Revised for v1.03
	void CheckProgramOperations(string&);  // Revised for v1.03
	bool IsGettingInstructions();          // Revised for v1.03
	bool FindsProgramName(string entry);

private:
	// Fields:
	int level;                             // New in v1.03
	int selection[25];                     // New in v1.03
	vector<Program> programs;

	// Methods:
	bool OverwritingProgram();
	bool ProgramNameIsValid(string);       // New in v1.03
	bool IsNumericEntry(string);           // New in v1.03
	bool IsVariableOrOperator(string);     // New in v1.03
	bool IsReservedKeyword(string);        // New in v1.03
	int ProgramIndex(string);
	string ProgramName();                  // Revised for v1.03
};


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:
	int functionIndex;               // Used to call a math function via a switch statement
	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
	ProgramManager pgmManager;       // Creates and maintains programs and program files

	// Methods:
	void TrapProgramName();
	void PushNumericValue();
	void PushVariableValue();
	void DeployOperator();
	void DeployMathFunction();
	void DeployStackFunction();
	void ReportUnsupportedEntry();

	bool GettingInstructions();
	bool EntryIsNumeric();
	bool EntryIsOperatorName();
	bool EntryIsVariableName();
	bool LastEntryIsVariableName();
	bool EntryIsStackFunctionName(); 
	bool EntryIsMathFunctionName();

	int MathFunctionIndex();
};


// Program: Constructors: _________________________________________________________________________

Program::Program() { 
	name = ""; 
	count = 0;
}	

Program::Program(string nm) {
	name = nm;
	string entry = "";
	cout << "Enter a sequence of program instructions; terminate by entering \"end\": ";
	while (cin >> entry && entry != "end")
		instructions.push_back(entry);
	cout << "   Program \"" << name << "\" created.\n";
}

// Program: Public methods: _______________________________________________________________________

void Program::ResetCounter() { 
	count = 0; 
}

void Program::Flush() { 
	name = ""; 
	count = 0; 
	instructions.resize(0); 
}

bool Program::IsFinished() { 
	return count == instructions.size(); 
}

string Program::Name() { 
	return name; 
}

string Program::NextInstruction() { 
	return instructions[count++]; 
}


// ProgramManager: Constructor and public methods: ________________________________________________

// Revised for v1.03:
ProgramManager::ProgramManager() { 
	level = 0; 
	selection[level] = -1; 
}

// Revised for v1.03:
void ProgramManager::CheckProgramOperations(string& entry) {
	// If creating a new program...
	if (entry == "pgm") {
		Program newProgram(ProgramName());
		programs.push_back(newProgram);
	}
	// If running a program at any level...
	if (level > 0) {
		entry = programs[selection[level]].NextInstruction();
		if (programs[selection[level]].IsFinished())
			--level;
	}
	// If a program label is encountered...
	int trial = ProgramIndex(entry);
	if (trial >= 0) {                                   // If the program is found inside the vector,...
 		selection[++level] = trial;                 // ...advance the level and select program; then...
		programs[selection[level]].ResetCounter();  // ...remotely set its instruction counter to 0
	}
	// Otherwise, the instruction is entered manually.
}

// Revised for v1.03:
bool ProgramManager::IsGettingInstructions() { 
	return level > 0; 
}

bool ProgramManager::FindsProgramName(string entry) { 
	return entry == "pgm" || ProgramIndex(entry) >= 0; 
}

// ProgramManager: Private methods: _______________________________________________________________

bool ProgramManager::OverwritingProgram() {
	char response = 0;
	while (response != 'Y' && response != 'y' && response != 'N' && response != 'n') {
		cout << "   A program of this name already exists. Do you want to overwrite it? (y/n) ";
		cin >> response;
	}
	return response == 'Y' || response == 'y';
}

// New in v1.03:
bool ProgramManager::ProgramNameIsValid(string trial) {
	if (IsNumericEntry(trial) || IsVariableOrOperator(trial) || IsReservedKeyword(trial))
		return false;
	return true;
}

// New in v1.03:
bool ProgramManager::IsNumericEntry(string trial) {
	if ((isdigit(trial[0])) ||
		(trial[0] == '.' && isdigit(trial[1])) ||
		(trial[0] == '-' && isdigit(trial[1])) ||
		(trial[0] == '-' && trial[1] == '.' && isdigit(trial[2]))) {
		cout << "   INVALID PROGRAM NAME: \"" << trial << "\" is a numeric entry.\n";
		return true;
	}
	return false;
}

// New in v1.03:
bool ProgramManager::IsVariableOrOperator(string trial) {
	if (trial.length() == 1) {
		string invalidChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ+-*/%=";
		for (int i = 0; i < 32; ++i) {
			if (trial[0] == invalidChars[i]) {
				cout << "   INVALID PROGRAM NAME: \"" << trial << "\" is ";
				(i < 26) ? cout << "a variable name.\n" : cout << "an operator name.\n";
				return true;
			}
		}
	}
	return false;
}

// New in v1.03:
bool ProgramManager::IsReservedKeyword(string trial) {
	string keywords[] = {
		"pgm", "end", "?", "dp", "sw", "cl", "pi", "e", "sin", "cos", "tan", 
		"asin", "acos", "atan", "deg", "rad", "sinh", "cosh", "tanh", "exp", "ln", "log", 
		"sqrt", "sq", "cbrt", "cb", "pr", "ceil", "floor", "round", "r", "chs", "abs", "yx"
	};
	for (int i = 0; i < 34; ++i) {
		if (trial == keywords[i]) {
			cout << "   INVALID PROGRAM NAME: \"" << trial << "\" is a reserved keyword.\n";
			return true;
		}
	}
	return false;
}

// Returns the index of the program being searched. If no
// program exists by the name given, function returns -1:
int ProgramManager::ProgramIndex(string entry) {
	for (int i = 0; i < programs.size(); ++i)
		if (programs[i].Name() == entry)
			return i;
	return -1;
}

// Revised for v1.03:
string ProgramManager::ProgramName() {
	string name;
	int index;
	while (true) {
		cout << "Enter a name for the program: ";
		cin >> name;
		// Test name for validity before processing:
		if (ProgramNameIsValid(name)) {
			// If this program name isn't already spoken for...
			if ((index = ProgramIndex(name)) == -1)
				return name;
			// ...but if it is...
			if (OverwritingProgram()) {
				programs[index].Flush();   // TEMPORARY workaround
				return name;
			}
		}
	}
}


// 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() {
	functionIndex = -1;
	for (int i = 0; i < 26; ++i)
		variables[i] = 0.0;
	entry = "";
	lastEntry = "";
	cout.precision(12);
	cout << "\nA Tiny RPN Calculator (;-}=   v1.03\n\n";
}

void Calculator::Run() {
	while (GettingInstructions()) {
		pgmManager.CheckProgramOperations(entry);

		if (pgmManager.FindsProgramName(entry))
			TrapProgramName();
		else if (EntryIsNumeric())
			PushNumericValue();
		else if (EntryIsVariableName())
			PushVariableValue();
		else if (EntryIsOperatorName())
			DeployOperator();
		else if (EntryIsMathFunctionName())
			DeployMathFunction();
		else if (EntryIsStackFunctionName())
			DeployStackFunction();
		else
			ReportUnsupportedEntry();

		lastEntry = entry;
	}
}

// Calculator: Private methods: ___________________________________________________________________

// Yes, it has an empty function body. The point is to keep legitimate program names
// from falling to the bottom of the if-else block, thus raising error messages 
// claiming that they're names of unsupported functions or nonexistent programs:
void Calculator::TrapProgramName() {
}

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::DeployMathFunction() {
	int prec;
	double arg1;
	const double pi = 3.141592653589793;

	switch (functionIndex) {
		// Push some constants onto the stack: __________________________________
		case 0:
			stack.Push(pi);
			break;
		case 1:
			stack.Push(2.718281828459045);
			break;

		// Trigonometric functions: _____________________________________________
		case 2:
			stack.Push(sin(stack.Pop()));
			break;
		case 3:
			stack.Push(cos(stack.Pop()));
			break;
		case 4:
			stack.Push(tan(stack.Pop()));
			break;
		case 5:
			stack.Push(asin(stack.Pop()));
			break;
		case 6:
			stack.Push(acos(stack.Pop()));
			break;
		case 7:
			stack.Push(atan(stack.Pop()));
			break;
		case 8:	// Converts radians to degrees
			stack.Push(stack.Pop() * 180.0 / pi);	
			break;
		case 9:	// Converts degrees to radians
			stack.Push(stack.Pop() * pi / 180.0);	
			break;

		// Hyperbolic functions: ________________________________________________
		case 10:
			stack.Push(sinh(stack.Pop()));	
			break;
		case 11:
			stack.Push(cosh(stack.Pop()));	
			break;
		case 12:
			stack.Push(tanh(stack.Pop()));
			break;

		// Exponential and logarithmic functions: _______________________________
		case 13: // Natural exponent ("e to the x"):
			stack.Push(exp(stack.Pop()));		
			break;
		case 14: // Natural log x (base e):
			stack.Push(log(stack.Pop()));		
			break;
		case 15: // Common log x (base 10):
			stack.Push(log10(stack.Pop()));	
			break;
		case 16: // Square root:
			stack.Push(sqrt(stack.Pop()));				
			break;
		case 17: // Square:
			stack.Push(pow(stack.Pop(), 2.0));			
			break;
		case 18: // Cube root:
			stack.Push(pow(stack.Pop(), 1.0 / 3.0));	
			break;
		case 19: // Cube:
			stack.Push(pow(stack.Pop(), 3.0));			
			break;

		// Functions for formatting numbers: ____________________________________
		case 20: // Resets output precision on the fly:
			prec = stack.Pop();				
			if (prec >= 0 && prec <= 16)
				cout.precision(prec);
			else
				cout << "   ERROR: " << prec << " not a valid setting for precision (0 <= precision <= 16 is valid).\n";
			break;
		case 21: // Rounds up:
			stack.Push(ceil(stack.Pop()));			
			break;
		case 22: // Rounds down:
			stack.Push(floor(stack.Pop()));			
			break;
		case 23: // Rounds to nearest whole number:
			stack.Push(floor(stack.Pop() + 0.5));	
			break;

		// Other useful math functions: _________________________________________
		case 24: // Reciprocation:
		arg1 = stack.Pop();						
		if (arg1 != 0.0)
			stack.Push(1.0 / arg1);
		else
			cout << "   ERROR: Attempted division by zero.\n";
			break;
		case 25: // Change sign:
			stack.Push(-(stack.Pop()));		
			break;
		case 26: // Absolute value:
			stack.Push(fabs(stack.Pop()));	
			break;
		case 27: // The power function ("y to the x"):
			arg1 = stack.Pop();					
			stack.Push(pow(stack.Pop(), arg1));
			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::ReportUnsupportedEntry() {
	cout << "   ERROR: \"" << entry << "\" is neither a supported function nor a program name.\n";
}

bool Calculator::GettingInstructions() {
	return pgmManager.IsGettingInstructions() || cin >> entry;
}

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"; 
}

bool Calculator::EntryIsMathFunctionName() {
	functionIndex = MathFunctionIndex();
	return functionIndex >= 0;
}

// Returns the index of a function name that matches the entry;
// if no match is found, returns -1:
int Calculator::MathFunctionIndex() {
	string functionNames[] = {
		"pi", "e", "sin", "cos", "tan", "asin", "acos", "atan", "deg", "rad", 
		"sinh", "cosh", "tanh", "exp", "ln", "log", "sqrt", "sq", "cbrt", "cb", 
		"pr", "ceil", "floor", "round", "r", "chs", "abs", "yx"
	};
	for (int i = 0; i < 28; ++i)
		if (entry == functionNames[i])
			return i;
	return -1;
}


// The main() function: ___________________________________________________________________________

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