/* ________________________________________________________________________________________________________
rpn102.cpp A Tiny RPN Calculator (;-}= v1.02
Author: Gordon M. Brown Revised 2009.08.02
XGB Web and Software Design www.xgbdesign.com
This draft is an extensive revision of v1.01. The chief objective is to enable the user to
write and invoke not just one program but as many as he or she wishes. The method Calculator::
DeployMathFunction() has also been converted from an if-else block to a switch statement.
For additional documentation of this source file, visit
http://www.xgbdesign.com/c++code/rpn-calculator/version1-02.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: ____________________________________________________________________________
// Completely revised for v1.02:
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;
};
// New in v1.02:
class ProgramManager {
public:
ProgramManager();
void CheckProgramOperations(string&);
bool IsGettingInstructions();
bool FindsProgramName(string entry);
private:
// Fields:
bool isActive;
int selection;
vector<Program> programs;
// Methods:
bool OverwritingProgram();
int ProgramIndex(string);
string ProgramName();
};
class Stack {
public:
Stack();
void Clear();
void Push(double);
double Pop();
private:
// Fields:
int freeStackPosition;
double elements[100];
};
class Calculator {
public:
Calculator(); // Revised for v1.02
void Run(); // Revised for v1.02
private:
// Fields:
int functionIndex; // Used to call a math function via a switch statement New in v1.02
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 New in v1.02
// Methods:
void TrapProgramName(); // New in v1.02
void PushNumericValue();
void PushVariableValue();
void DeployOperator();
void DeployMathFunction(); // Revised for v1.02
void DeployStackFunction();
void ReportUnsupportedEntry(); // New in v1.02
bool GettingInstructions(); // New in v1.02
bool EntryIsNumeric();
bool EntryIsOperatorName();
bool EntryIsVariableName();
bool LastEntryIsVariableName();
bool EntryIsStackFunctionName();
bool EntryIsMathFunctionName(); // New in v1.02
int MathFunctionIndex(); // New in v1.02
};
// 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: ________________________________________________
ProgramManager::ProgramManager() {
isActive = false;
selection = -1;
}
void ProgramManager::CheckProgramOperations(string& entry) {
int trial;
// If creating a new program...
if (entry == "pgm") {
Program newProgram(ProgramName());
programs.push_back(newProgram);
}
// If launching an existing program by name...
// (and if program doesn't exist, the label just falls through)
if ((trial = ProgramIndex(entry)) >= 0) {
isActive = true;
selection = trial;
programs[selection].ResetCounter(); // Remotely sets instruction counter to 0
}
// If the program is running...
if (isActive) {
entry = programs[selection].NextInstruction();
if (programs[selection].IsFinished())
isActive = false;
}
// Otherwise, the instruction is entered manually.
}
bool ProgramManager::IsGettingInstructions() {
return isActive;
}
bool ProgramManager::FindsProgramName(string entry) {
return entry == "pgm" || ProgramIndex(entry) >= 0;
}
// ProgramManager: Private methods: _______________________________________________________________
bool ProgramManager::OverwritingProgram() {
char ans = 0;
while (ans != 'Y' && ans != 'y' && ans != 'N' && ans != 'n') {
cout << " A program of this name already exists. Do you want to overwrite it? (y/n) ";
cin >> ans;
}
return (ans == 'Y' || ans == 'y');
}
// 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;
}
string ProgramManager::ProgramName() {
string name;
int index;
while (true) {
cout << "Enter a name for the program: ";
cin >> 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: ____________________________________________________
// Revised for v1.02:
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.02\n\n";
}
// Revised for v1.02:
void Calculator::Run() {
while (GettingInstructions()) { // New in v1.02
pgmManager.CheckProgramOperations(entry); // New in v1.02
if (pgmManager.FindsProgramName(entry)) // New in v1.02
TrapProgramName(); // New in v1.02
else if (EntryIsNumeric())
PushNumericValue();
else if (EntryIsVariableName())
PushVariableValue();
else if (EntryIsOperatorName())
DeployOperator();
else if (EntryIsMathFunctionName()) // New in v1.02
DeployMathFunction(); // New in v1.02
else if (EntryIsStackFunctionName())
DeployStackFunction();
else
ReportUnsupportedEntry(); // New in v1.02
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;
}
}
// For v1.02, this method has been rewritten using a switch statement in place
// of the lengthy if-else statement. The new method MathFunctionIndex() directs
// program flow to this method by referencing the correct index for a math
// function chosen by the user:
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
}
// New in v1.02:
void Calculator::ReportUnsupportedEntry() {
cout << " ERROR: \"" << entry << "\" is neither a supported function nor a program name.\n";
}
// New in v1.02:
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";
}
// New in v1.02:
bool Calculator::EntryIsMathFunctionName() {
functionIndex = MathFunctionIndex();
return functionIndex >= 0;
}
// New in v1.02: 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;
}