______________________ RPN CALCULATOR AGAIN lro ______________________ <2019-01-04 Fri> Table of Contents _________________ New data structure for functions alist .. insertion .. update calling a function in the loop Initial Dictionary Main loop Conslusion I'm back with another Installment of the RPN calculator. Today I thought we could clean up that awful main loop, by have one expression for symbols, and keep a list of all the functions that we accept, and search throught it. I might also add a FORTH style syntax for defining your own functions. New data structure for functions ================================ I was thinking that we we need a new data structre for our functions, an alist where the car is the symbol we want to use as an identifier and the cdr being the function itself, taking one argument, the stack, and returning the stack. This will be our "dictionary". But as an exercise we will also include a very simple alist implementation. alist ===== We need some fundamental operations for an alist, retrieve value, insertion, deletion, update. Luckily R7RS provides some functions for us, like /assq/ which retrieve a value from an alist, and if it doesn't exist, return `false'. insertion ~~~~~~~~~ To insert into an alist we must search the alist and make sure that that key doesn't alredy exist, and if it doesn't then append it to our list with our chosen values. ,---- | (define (insert-into-alist key val alist) | (let ((mem? (assq key alist))) | (if mem? | (update-alist key val alist) | (append alist (list (cons key val)))))) `---- update ~~~~~~ To update an alist, we have to find the key, and change its associated value, and return the modified dictionary. We will also assume that /key/ exists in the alist. We need to find the index into the list whee our key is, so we traverse our alist until we find it, then we can use that index in /list-set!/ which isn't functional but the easiest way of doing it. ,---- | (define (index-in-alist key alist) | (let loop ((list (list-copy alist)) | (index 0)) | (if (= (length list) 0) | #f | (let ((list-head-key (car (car list)))) | (if (eq? list-head-key key) | index | (loop (cdr list) (+ index 1))))))) | | (define (update-alist key new-val alist) | (let ((index (index-in-alist key alist))) | (list-set! alist index (list (cons key new-val))) | alist)) `---- calling a function in the loop ============================== Now how is calling a function from our main loop going to work? Well first we need too check if the symbol is in the dictionary, if it isn't then we need to return an error. If it is then we need to use /assq/ o get the function and run it pasing it the stack. We will need a helper function, /run-func/ that takes the symbol, dictionary and the stack. ,---- | (define (run-func sym dict stack) | (let ((func (assq sym dict))) | (if func | ((cdr func) stack) | (begin | (display "ERROR: symbol not in dictionary: ") | (display sym) | (newline) | stack)))) `---- Initial Dictionary ================== This will be the initial dictionary that is passed to the main loop, it wil hold all of our "primitives" that we can add to later. ,---- | (define init-dict | (list (cons '$ (lambda (stack) | (let-values (((var stack) (pop stack))) | (display var) | (newline) | stack))) | (cons '+ (lambda (stack) (rpn-func + stack))) | (cons '- (lambda (stack) (rpn-func - stack))) | (cons '* (lambda (stack) (rpn-func * stack))) | (cons '/ (lambda (stack) (rpn-func / stack))) | (cons '% (lambda (stack) (rpn-func modulo stack))) | (cons '! (lambda (stack) | (let-values (((var stack) (pop stack))) | (push (fact var) stack)))) | (cons '/ (lambda (stack) (rpn-func / stack))) | (cons 'dup (lambda (stack) (dup stack))))) `---- Main loop ========= The main loop now only checks for symbols and passes any to our /run-func/ function. And we have added our dictionary to the loop, so that we can modify it in our REPL when we add that functionality. ,---- | (let loop ((input (read)) | (stack '()) | (dict init-dict)) | (cond | ((number? input) (loop (read) (push input stack) dict)) | ((symbol? input) (loop (read) (run-func input dict stack) dict)) | (else (begin | (display "ERROR not valid input: ") | (display input) | (newline) | (loop (read) stack dict))))) `---- Conslusion ========== So next time we will add the ability to add our own user defined functions. In the mean time feel free to email me some feedback at lro@sdf.org, or even try adding some functions to the initial dictionary yourself, like some trigonometry functions or vector math like dot or cross product.