________________ RPN CALCULATOR lro ________________ <2019-01-02 Wed> Table of Contents _________________ Introduction How I think it works The Stack Main Loop Calculator Functions factorial function Introduction ============ A common exercise for prorgramming is to implement a [Reverse Polish Notation] calculator, usually just with a commandline interface. I have never actually done this exercise before. So here is my first attempt in R7RS scheme. [Reverse Polish Notation] https://en.wikipedia.org/wiki/Reverse_Polish_notation How I think it works ==================== A simple way to implement an RPN calculator is to use a stack, and push each value onto the stack, then operate on the stack when you hit an operator like `+' or `/'. If you are familiar with Forth then this will sound pretty familiar. The Stack ========= So first we will define our stack, we will just use a list at the moment, and each new element of the stack will be pushed onto the head of the list. ,---- | (define *stack* '()) `---- Probably best to define the basic stack operations like /pop!/ and /push!/. ,---- | (define (pop!) | (let ((ret (car *stack*))) | (set! *stack* (cdr *stack*)) | ret)) | | (define (push! val) | (set! *stack* (append (list val) *stack*))) | | (define (dup!) | (set! *stack* (append (list (car *stack*)) *stack*))) `---- Main Loop ========= The main loop will consist of: 1. Wait for input from standard input. 2. Read each token in and check if either a number or an operator. 3. If operator, do operation. 4. If number, push onto stack. I have used a named let to loop through the standad input using /read/, then each which kind of acts like a tokenizer where it returns each scheme object for us from standard input. As you can see it is trivially easy to add more functions too this calculator by just adding a clause to the /cond/ by adding some syntax, and writing a new function that just has to interact with the stack. ,---- | (let loop ((input (read))) | (display "RPN> ") | (cond | ((number? input) (push! input)) | ((eq? '+ input) (rpn-add)) | ((eq? '- input) (rpn-sub)) | ((eq? '* input) (rpn-mult)) | ((eq? '/ input) (rpn-div)) | ((eq? '% input) (rpn-mod)) | ((eq? '! input) (push! (fact (pop!)))) | ((eq? '. input) (begin | (display (pop!)) | (newline))) | ((eq? 'dup input) (dup!)) | (else (begin | (display "ERROR not valid input: ") | (display input) | (newline)))) | (loop (read))) `---- Calculator Functions ==================== So as you can probably guess all the /rpn-*/ functions are just wrappers of scheme functions that get there arguments from the stack and their return values are pushed back onto it. For example addition: ,---- | (define (rpn-add) | (push! (+ (pop!) (pop!)))) `---- We could write a macro that expanded from something like `(rpn-func \+)' to `(push! (\+ (pop!) (pop!)))'. ,---- | (define-syntax rpn-func | (syntax-rules () | ((rpn-func x) | (push! (x (pop!) (pop!)))))) `---- Sweet! But i think it over complicates things a little bit, and also how would we deal with functions that only pop the stack once? or if a function has a diffeent name in scheme than what we want its RPN equivalent? So for now I will only use the macro in the main loop to replace the /rpn-*/ functions in the /cond/ that pop two stack elements and also have the same identifer in scheme and our RPN calculator. We might reconjigger things around if we can find a better way. ,---- | (let loop ((input (read))) | (cond | ((number? input) (push! input)) | ((eq? '+ input) (rpn-func +)) | ((eq? '- input) (rpn-func -)) | ((eq? '* input) (rpn-func *)) | ((eq? '/ input) (rpn-func /)) | ((eq? '% input) (push! (modulo (pop!) (pop!)))) | ((eq? '! input) (push! (fact (pop!)))) | ((eq? '^ input) (push! (pow (pop!) (pop!)))) | ((eq? '$ input) (begin | (display (pop!)) | (newline))) | ((eq? 'dup input) (dup!)) | (else (begin | (display "ERROR not valid input: ") | (display input) | (newline)))) | (loop (read))) `---- factorial function ================== A simple factorial implementation. ,---- | (define (fact x) | (define (fact-iter n current) | (if (= n 1) | current | (fact-iter (- n 1) (* n current)))) | (fact-iter x 1)) `---- And thats it for a simple commandline RPN calculator.