Prelude

# Prelude: A Reverse Polish Notation Calculator #

In order to get a footing, we’ll start with an example: a calculator that uses postfix operator notation, sometimes called “reverse polish notation.”

In this calculator, all arguments are placed onto a stack. Whenever an operator occurs, it pops values off the stack at that point, performs the operation, and pushes the answer back on.

All operations are binary, so they take two operands from the stack and replace it with one result. To simplify matters, if the stack is has only one operand on it when an operator is called, the other operand is zero.

The calculator takes one line of calculation at a time. If there are leftover elements, it will print the top of the stack, which was the result of the last operation.

Here are some examples, with `>` marking the user input prompt:

``````> 1
1
> 1 2
2
> 2 3 +
5
> 2 3 * 4 5 * +
26
> 2 3 4 + *
14
> 2 3 4 5 * * +
62
``````
Our Reference Implementation
 `````` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 `````` ``````# this is a stack of numbers being stored for operation number_stack = [] def push_num(n): number_stack.append(int(n)) def pop_num(): # the stack has a 0 "pinned" to the bottom of it if len(number_stack) == 0: return 0 else: return number_stack.pop() while 1: number_stack[:] = [] print("> ", end="") line = input() if line.startswith('q'): break # step one: break the line into pieces # keeping track of position parts = line.split() pos = 1 # not zero due to an offset in the prompt for part in parts: pos += len(part) # decide what the part of the string is. there are several options # 1. is it a number? append in on the stack if part.isdigit(): push_num(part) # 2. is it an addition? pop two items and add them, then append elif part == "+": push_num(pop_num() + pop_num()) # 3. is it a multiplication? same idea elif part == "x" or part == "*": push_num(pop_num() * pop_num()) # 4. if it is a blank, then that means we can skip it # otherwise, finally if it's anything else, ther is a syntax error elif part != '': print(" " * pos + "^") print("syntax error: unknown token") break pos += 1 # account for the space # in case of a syntax error, it was printed, so continue to the next line if pos == len(line) + 1: continue else: print(number_stack[-1]) ``````

DOWNLOAD - Note, you’ll need to put spaces between each token and press enter to run a line.

At a high level, it turns out the calculator is doing many of the phases of a compiler! Sort of.

It is doing lexical analysis: splitting up the program into numbers and the operators to execute on them.

It is doing syntax analysis: identifying what operations to do, and keeping track of the order to do them in, which determines their operands.

It is using those syntax understandings to take action. It executes them immediately, which makes it an interpreter rather than a compiler, and isn’t really “translation”, but the idea is still there.

It is so simple mostly because of the simplicty of the “language.” It has only one type (integer), no variables, and not even any control flow. In fact, it is not even Turing Complete!Aside from the lack of control flow, this is because our abstract machine requires data to be accessed in stack order (LIFO).

Even if memory and the number of operations were infinite (as is the case in a Turing machine), this data access pattern makes certain problems uncomputable. In order to make it Turing complete, at least one operation that allows random access (i.e. read from the middle without deleting the data above it) is necessary. This was proven several decades ago.

It would suffice, for example, to add a second parallel stack, and let operations choose between them. It would also suffice to force data accesses into queue order (FIFO) instead.

While there is no way to make this into a language like C or Python without a total re-write, perhaps we can fix these shortcomings and come up with something more powerful in the course of these sections. And perhaps we can learn something along the way.

To make things easier to follow, and facilitate changing one piece at a time, I’m going to create a python package called `calc_lang`. If you’re not familiar with python packages, here’s an overview. From now on, all “final code” examples will be in the package.

If you would like to support my development of OpGuides, please consider supporting me on GitHub Sponsors or dropping me some spare change on Venmo @vegadeftwing - every little bit helps ❤️