/* ________________________________________________________________________________________________________
rpn106.cpp A Tiny RPN Calculator (;-}= v1.06
Author: Gordon M. Brown Revised 2009.11.01
XGB Web and Software Design www.xgbdesign.com
Version 1.06 enables the calculator to store the argument (or arguments) of the most recent
calculation so that the user can recall them when necessary and push them onto the stack.
For additional documentation of this source file, visit
http://www.xgbdesign.com/c++code/rpn-calculator/version1-06.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 <fstream>
#include <cmath>
#include <vector>
using namespace std;
// Class declarations: ____________________________________________________________________________
class Program {
public:
Program();
Program(string);
// Mutator methods:
void ResetCounter();
void Flush();
void SetName(string); // For reading from a file
void SetInstruction(string); // For reading from a file
// Read-only accessor methods:
bool IsFinished();
string Name();
string NextInstruction();
private:
// Fields:
int count;
string name;
vector<string> instructions;
};
// Revised for v1.06:
class ProgramManager {
public:
ProgramManager();
void CreateProgram();
void RecallProgram();
void PassInstructionTo(string&);
void SearchForProgramNamed(string);
// Read-only accessor methods:
bool IsGettingInstructions();
bool FindsProgramName(string);
private:
// Fields:
bool overwritingFile;
int level;
int selection[25];
vector<Program> programs;
// Methods:
void Read(Program&);
void Write(Program&);
void WriteToScreen(Program&);
bool SavingProgram(string);
bool OverwritingProgram(string);
bool OverwritingProgramFile(string);
bool EndingRecall(string);
bool Response(string, int);
bool ProgramExists(string);
bool ProgramFileExists(string);
bool ProgramNameIsValid(string);
bool IsNumericEntry(string);
bool IsVariableOrOperator(string);
bool IsReservedKeyword(string); // Revised for v1.06
int ProgramIndex(string);
string ProgramName();
const char* FileName(string);
};
class Stack {
public:
Stack();
void Clear();
void Push(double);
double Pop();
private:
// Fields:
int freeStackPosition;
double elements[100];
};
// New in v1.06:
class Arguments {
public:
Arguments();
void Record(Stack&, int);
void Restore(Stack&);
private:
// Fields:
int arity;
double value1, value2;
};
// Revised for v1.06:
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
Arguments lastArguments; // Implements storage and retrieval of last argument(s) New in v1.06
// Methods:
void TrapProgramName();
void PushNumericValue();
void PushVariableValue();
void DeployOperator(); // Revised for v1.06
void DeployMathFunction(); // Revised for v1.06
void DeployStackFunction(); // Revised for v1.06
void DeployStringFunction();
void ReportUnsupportedEntry();
bool GettingInstructions();
bool EntryIsNumeric();
bool EntryIsOperatorName();
bool EntryIsVariableName();
bool LastEntryIsVariableName();
bool EntryIsStackFunctionName();
bool EntryIsMathFunctionName();
bool EntryIsStringName();
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);
}
void Program::SetName(string str) {
name = str;
}
void Program::SetInstruction(string str) {
instructions.push_back(str);
}
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() {
overwritingFile = false;
level = 0;
selection[level] = -1;
}
void ProgramManager::CreateProgram() {
string newName = ProgramName();
Program newProgram(newName);
if (SavingProgram(newName))
Write(newProgram);
int index = ProgramIndex(newName);
if (index >= 0) // If an older version exists in the vector,...
programs[index].Flush(); // ...then flush it. (TEMPORARY workaround.)
programs.push_back(newProgram);
}
void ProgramManager::RecallProgram() {
string name;
bool searching = true;
while (searching) {
cout << "Enter name of program: ";
cin >> name;
if (ProgramNameIsValid(name)) {
if (ProgramFileExists(name)) {
Program thisProgram;
thisProgram.SetName(name);
Read(thisProgram);
WriteToScreen(thisProgram);
searching = false;
}
else if (ProgramExists(name)) {
WriteToScreen(programs[ProgramIndex(name)]);
searching = false;
}
else if (EndingRecall(name))
searching = false;
}
}
}
// Here the entry is passed by reference because it will
// be changed as the program feeds new instructions:
void ProgramManager::PassInstructionTo(string& entry) {
if (level > 0) {
entry = programs[selection[level]].NextInstruction();
if (programs[selection[level]].IsFinished())
--level;
}
}
void ProgramManager::SearchForProgramNamed(string entry) {
int index = -1;
bool searching = true;
while (searching) {
index = ProgramIndex(entry);
if (index >= 0) { // If the program is found inside the vector,...
selection[++level] = index; // ...advance the level and select program; then...
programs[selection[level]].ResetCounter(); // ...remotely set its instruction counter to 0; lastly,...
searching = false; // ...end the search.
}
else if (ProgramFileExists(entry)) { // If it's not in the vector but a file of that name exists,...
Program newProgram; // ...create a blank program using default constructor;...
newProgram.SetName(entry); // ...set the name of the program;...
Read(newProgram); // ...read its contents from the file; then...
programs.push_back(newProgram); // ...push it into the vector and cycle back to the top.
}
else // Otherwise, no program of that name exists, so the search is ended;
searching = false; // the entry is presumed to be a program instruction instead, and
} // simply falls through.
}
bool ProgramManager::IsGettingInstructions() {
return level > 0;
}
bool ProgramManager::FindsProgramName(string entry) {
return entry == "pgm" || entry == "rcl" || ProgramIndex(entry) >= 0;
}
// ProgramManager: Private methods: _______________________________________________________________
void ProgramManager::Read(Program& pgm) {
string pgmName = pgm.Name();
ifstream fin(FileName(pgmName));
if (fin.fail())
cout << " ERROR: Attempt to open input file \"" << pgmName << ".pf\" failed.\n";
else {
string next;
while (fin >> next)
pgm.SetInstruction(next);
}
fin.close();
}
void ProgramManager::Write(Program& pgm) {
string pgmName = pgm.Name();
ofstream fout(FileName(pgmName));
if (fout.fail())
cout << " ERROR: Attempt to open output file \"" << pgmName << ".pf\" failed.\n";
else {
pgm.ResetCounter();
while (pgm.IsFinished() == false)
fout << pgm.NextInstruction() << " ";
fout.close();
cout << " Program \"" << pgmName << "\" saved to file \"" << pgmName << ".pf\".\n";
}
}
void ProgramManager::WriteToScreen(Program& pgm) {
cout << " Program \"" << pgm.Name() << "\" recalled: ";
pgm.ResetCounter();
while (pgm.IsFinished() == false)
cout << pgm.NextInstruction() << " ";
cout << endl;
}
// Reads the flag "overwritingFile" and, depending on the
// flag's value, either bypasses or dispatches the query:
bool ProgramManager::SavingProgram(string pgmName) {
if (overwritingFile) {
overwritingFile = false;
return true;
}
return Response(pgmName, 0);
}
bool ProgramManager::OverwritingProgram(string pgmName) {
return Response(pgmName, 1);
}
// Sets the flag "overwritingFile" to true or false depending
// on the user's response, then returns its value:
bool ProgramManager::OverwritingProgramFile(string fileName) {
overwritingFile = Response(fileName, 2);
return overwritingFile;
}
bool ProgramManager::EndingRecall(string pgmName) {
return Response(pgmName, 3);
}
bool ProgramManager::Response(string name, int queryNumber) {
// Formulate a query:
string query = " ";
switch (queryNumber) {
case 0:
query += ("Save \"" + name + "\" to file?");
break;
case 1:
query += ("The program \"" + name + "\" already exists. Do you want to overwrite it?");
break;
case 2:
query += ("PLEASE NOTE: The file \"" + name +
".pf\" already exists. Are you sure you want to overwrite the file?");
break;
case 3:
query += ("ERROR: The program \"" + name + "\" doesn't exist. End recall?");
break;
}
query += " (y/n) ";
// Use query to generate a Boolean response from the user:
char response = 0;
while (response != 'Y' && response != 'y' && response != 'N' && response != 'n') {
cout << query;
cin >> response;
}
return response == 'Y' || response == 'y';
}
bool ProgramManager::ProgramExists(string name) {
return ProgramIndex(name) >= 0;
}
bool ProgramManager::ProgramFileExists(string name) {
ifstream fin(FileName(name));
bool exists = (fin.fail() == false);
if (exists)
fin.close();
return exists;
}
bool ProgramManager::ProgramNameIsValid(string trial) {
if (IsNumericEntry(trial) || IsVariableOrOperator(trial) || IsReservedKeyword(trial))
return false;
return true;
}
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;
}
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;
}
// For v1.06, the strings "la" and "dr" are added to the list of reserved keywords:
bool ProgramManager::IsReservedKeyword(string trial) {
string keywords[] = {
"pgm", "end", "rcl", "nl", "?", "$", "dp", "sw", "la", "dr", "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", "max", "min", "%of", "%ch", "%t"
};
for (int i = 0; i < 44; ++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;
}
string ProgramManager::ProgramName() {
string name;
while (true) {
cout << "Enter a name for the program: ";
cin >> name;
if (ProgramFileExists(name)) {
if (OverwritingProgramFile(name))
return name;
}
else if (ProgramExists(name)) {
if (OverwritingProgram(name))
return name;
}
else if (ProgramNameIsValid(name))
return name;
}
}
const char* ProgramManager::FileName(string pgmName) {
string fileName = pgmName + ".pf";
return fileName.c_str();
}
// 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;
}
}
// Arguments: Constructor and public methods: _____________________________________________________
// New in v1.06:
Arguments::Arguments() {
arity = 0;
value1 = value2 = 0.0;
}
// New in v1.06:
void Arguments::Record(Stack& s, int count) {
arity = count;
if (arity == 1) {
value1 = s.Pop();
s.Push(value1);
}
else {
value1 = s.Pop();
value2 = s.Pop();
s.Push(value2);
s.Push(value1);
}
}
// New in v1.06:
void Arguments::Restore(Stack& s) {
if (arity == 1)
s.Push(value1);
else {
s.Push(value2);
s.Push(value1);
}
}
// 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.06\n\n";
}
void Calculator::Run() {
while (GettingInstructions()) {
if (entry == "pgm")
pgmManager.CreateProgram();
if (entry == "rcl")
pgmManager.RecallProgram();
pgmManager.PassInstructionTo(entry);
pgmManager.SearchForProgramNamed(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 if (EntryIsStringName())
DeployStringFunction();
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']);
}
// Revised for v1.06:
void Calculator::DeployOperator() {
double arg1;
if (entry[0] != '=')
lastArguments.Record(stack, 2);
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;
}
}
// Revised for v1.06:
void Calculator::DeployMathFunction() {
int arity, prec;
double arg1, arg2;
const double pi = 3.141592653589793;
// Functions indexed at 2 through 26 take one argument,
// whereas those from 27 through 32 take two:
if (functionIndex >= 2) {
arity = (functionIndex <= 26) ? 1 : 2;
lastArguments.Record(stack, arity);
}
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;
case 28: // Maximum of x and y:
arg1 = stack.Pop();
arg2 = stack.Pop();
(arg1 >= arg2) ? stack.Push(arg1) : stack.Push(arg2);
break;
case 29: // Minimum of x and y:
arg1 = stack.Pop();
arg2 = stack.Pop();
(arg1 <= arg2) ? stack.Push(arg1) : stack.Push(arg2);
break;
case 30: // y percent of x (same as x percent of y):
stack.Push(stack.Pop() * stack.Pop() / 100.0);
break;
case 31: // Percent change from x to y as percentage of x:
arg1 = stack.Pop();
arg2 = stack.Pop();
if (arg2 != 0.0)
stack.Push((arg1 - arg2) / arg2 * 100.0);
else
cout << " ERROR: Attempted division by zero.\n";
break;
case 32: // Percent total (x/y, in percent):
arg1 = stack.Pop();
arg2 = stack.Pop();
if (arg2 != 0.0)
stack.Push(arg1 / arg2 * 100.0);
else
cout << " ERROR: Attempted division by zero.\n";
break;
}
}
// For v1.06, "la" added to enable recall of most recent argument(s),
// which are then pushed onto the stack; and "dr", which drops top element:
void Calculator::DeployStackFunction() {
double arg1, arg2;
if (entry == "?" || entry == "$") { // If querying, or preparing program output,...
arg1 = stack.Pop(); // ...output top element in stack
stack.Push(arg1);
cout << " " << arg1;
if (entry == "?")
cout << 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 == "la")
lastArguments.Restore(stack); // Push last argument(s) onto the stack
else if (entry == "dr")
stack.Pop(); // Drop top element
else if (entry == "cl")
stack.Clear(); // Clear the stack
}
void Calculator::DeployStringFunction() {
(entry == "nl") ? cout << endl : cout << " " << entry.substr(1, entry.length() - 2);
}
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;
}
// Revised for v1.06:
bool Calculator::EntryIsStackFunctionName() {
return entry == "?" || entry == "$" || entry == "dp" || entry == "sw"
|| entry == "la" || entry == "dr" || entry == "cl";
}
bool Calculator::EntryIsStringName() {
return entry == "nl" || (entry[0] == '\'' && entry[entry.length() - 1] == '\'');
}
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", "max", "min", "%of", "%ch", "%t"
};
for (int i = 0; i < 33; ++i)
if (entry == functionNames[i])
return i;
return -1;
}
// The main() function: ___________________________________________________________________________
int main() {
Calculator tinyCalculator;
tinyCalculator.Run();
return 0;
}