3/5/23
I use this type of calculator when I do arithmetic. Popular with accountants, the OG was the HP-16C (iconic). HP still makes the HP Prime (which I used in high school). My favorite implementation is by PCalc on iPhone.
RPNCalc is an implementation of a postfix calculator, where a binary operator is placed after its two operands. Calculators of this type are said to use "Reverse Polish Notation". RPNCalc uses the standard input stream to take three types of input: integers, boolean values, and small "subprograms" in the format of an "rstring". Operations mirror those normally seen on common RPN calculators, like the classic HP12C. These include swap, drop, (you'll see these in PCalc) and the like. Reverse Polish notation and postfix notation eliminate the ambiguity of expressions, removing the need for perentheses, increasing clarity and efficiency.
-
main.cpp - RPNCalc's executor, simply creates and runs an RPNCalc object
-
RPNCalc.h - defines methods for the RPNCalc class, outlining usable commands; defines the calculator stack and con/destructor
-
RPNCalc.cpp - implements the RPNCalc class, handling the majority of functionality within RPNCalc; starts a query loop to handle input, calls helper functions on a per-input basis
-
DatumStack.h - defines a stack structure for Datum objects, with usual methods like
pop,push, etc. This class handles theDatumobject exclusively, and is not a template -
DatumStack.cpp - implements the DatumStack stack structure, using an
std::listas a backbone; a very simple class -
parser.h - a header file for the singleton
parseRStringfunction, which begins handling a user-inputtedrstringas soon as the first{, finishing upon a symmetric} -
parser.cpp - the implementaiton of
parseRString, leveraging recursion for nestedrstrings, returningstd::string -
Makefile - The GNU compiling and linking automator file, containing rules for each class and function within RPNCalc; also contains cleaning rules and critical testing automation, like
diff_testandunit_test -
unit_tests.h - Creates a seperate
mainfile for each void function... for testing
Compile this program by calling make in a CLI. You may also call make RPNCalc. This generates an executable called RPNCalc, which can be run in a CLI as ./RPNCalc. The program takes input from the standard input stream, but can handle redirected input.
Commands may be found in RPNCalc.h and their detailed actions in RPNCalc.cpp in corresponding function contracts, but I will also list them briefly here:
[n]: pushesn(an integer) to the calculator stack#t: pushes#t(true) to the calculator stack#f: pushes#f(false) to the calculator stacknot: reverses the boolean at the top of the stackclear: clears the stackdrop: drops the top member of the stackdup: duplicates the top member of the stackswap: swaps the top and second-to-top members of the stackprint: prints the top member of the stack to the standard outstream+ - * / mod: valid arithmetic operators working in postfix notation== > >= < <=: valid comparison operators working in postfix notation[rstring]: pushes a not-yet-executed set of cmds to the stack (see rstring)exec: executes the contents of the rstring at the top of the stackfile: reads the top rstring and execs the contents from its contained fileIF: pops: rstring1, rstring1, bool, executes 1 if false, 2 if truequit: quits the calculator
Worth elaboring on is the file command. Small subprograms may be built via the .cyl interface, which Mark Sheldon can tell you more about. These files are translated into .cylc files, which can be read by the file command and executed within the RPNCalc program. They can be incredibly powerful.
I tested RPNCalc in two ways.
I tested the core data structure of RPNCalc, the DatumStack, using the unit_test framework. Because DatumStack has entirely public methods, unit_test proved easy to use without workarounds. Intended behavior was clear for the DatumStack class, even without reference implementation. This made unit_test (unable to compare inputs of two different programs) a perfect testing method. These tests can be run via the command unit_test, and were created before the creation of the RPNCalc class.
I tested the RPNCalc class (and the program as a whole) using a diff-automation framework included in my Makefile. In many cases of the RPNCalc class, without a reference implementation, the correct course of behavior would be up to judgement. Eg. in the reference implementation, when calling a command like exec, three members of the stack are popped before we check their types, discarding them if their types are faulty. Is this really the "right" way to do something? We couldn't test for something like this via the unit_test framework. We need to direcly compare every output of the RPNCalc class to the reference ./theRPNCalc program in order to understand our source of truth. I wrote these functions as I defined RPNCalc's methods but only tested them upon RPNCalc's completion.
These files come in two different forms. Files with the txt extension are redirected to the standard input stream. They are handled just like user input. Files with the cylc extension are read in via the <fstream> library and executed in accordance with the file command.
Diffing must be done in a bash shell, redirecting not only the standard output but also the standard error stream to the outfile. Type bash to enter one of these shells.
.txt files tests can be run via diff_test [input.txt]. These make up the majority of the tests.