/* ________________________________________________________________________________________________ rpn104.txt ~ rev. 2011.08.12 XGB Web and Software Design ~ www.xgbdesign.com Version 1.04 extends the Tiny Calculator by enabling users to store programs in files. For additional documentation of this source file, visit http://www.xgbdesign.com/c++code/rpn-calculator/version1-04.html. PLEASE NOTE: This source code file has been saved with the ".txt" extension to obviate browser difficulties, particularly with Internet Explorer. To compile and run this code, just copy and paste this text into a new document, but save the file with the extension ".cpp" PLEASE NOTE: This code is licensed under the terms of the GNU General Public License, Version 3 (http://www.gnu.org/copyleft/gpl.html#header). You are free to use and modify this source code, but if you do, kindly cite XGB Web and Software Design, whether in a link from your website, or or in your own source code. Thanks! ________________________________________________________________________________________________ */ #include #include // New in v1.04 #include #include using namespace std; // Class declarations: ____________________________________________________________________________ // Revised for v1.04: class Program { public: Program(); Program(string); // Mutator methods: void ResetCounter(); void Flush(); void SetName(string); // For reading from a file New in v1.04 void SetInstruction(string); // For reading from a file New in v1.04 // Read-only accessor methods: bool IsFinished(); string Name(); string NextInstruction(); private: // Fields: int count; string name; vector instructions; }; // Revised for v1.04: class ProgramManager { public: ProgramManager(); // Revised for v1.04 void CreateProgram(); // New in v1.04 void PassInstructionTo(string&); // New in v1.04 void SearchForProgramNamed(string); // New in v1.04 // Read-only accessor methods: bool IsGettingInstructions(); bool FindsProgramName(string); private: // Fields: bool overwritingFile; // New in v1.04 int level; int selection[25]; vector programs; // Methods: void Read(Program&); // New in v1.04 void Write(Program&); // New in v1.04 bool SavingProgram(string); // New in v1.04 bool OverwritingProgram(string); // Revised for v1.04 bool OverwritingProgramFile(string); // New in v1.04 bool Response(string, int); // New in v1.04 bool ProgramExists(string); // New in v1.04 bool ProgramFileExists(string); // New in v1.04 bool ProgramNameIsValid(string); bool IsNumericEntry(string); bool IsVariableOrOperator(string); bool IsReservedKeyword(string); int ProgramIndex(string); string ProgramName(); const char* FileName(string); // New in v1.04 }; 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); } // New in v1.04: void Program::SetName(string str) { name = str; } // New in v1.04: 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: ________________________________________________ // Revised for v1.04: ProgramManager::ProgramManager() { overwritingFile = false; level = 0; selection[level] = -1; } // New in v1.04: 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); } // New in v1.04: 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; } } // New in v1.04: 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" || ProgramIndex(entry) >= 0; } // ProgramManager: Private methods: _______________________________________________________________ // New in v1.04: 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(); } // New in v1.04: 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"; } } // New in v1.04: 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); } // Revised for v1.04: bool ProgramManager::OverwritingProgram(string pgmName) { return Response(pgmName, 1); } // New in v1.04: 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; } // New in v1.04: 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; } 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'; } // New in v1.04: bool ProgramManager::ProgramExists(string name) { return ProgramIndex(name) >= 0; } // New in v1.04: 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; } 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.04: 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; } } // New in v1.04: 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; } } // 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.04\n\n"; } // Revised for v1.04: void Calculator::Run() { while (GettingInstructions()) { if (entry == "pgm") pgmManager.CreateProgram(); // New in v1.04 pgmManager.PassInstructionTo(entry); // New in v1.04 pgmManager.SearchForProgramNamed(entry); // New in v1.04 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; }