_____________________________ PROGRAMMABLE RPN CALCULATOR lro _____________________________ <2019-01-05 Sat> Table of Contents _________________ More primitives User defined functions (attempt 1) .. user-func-from-list User defined functions (Attempt 2) Today I am going to add the ability to create user defined functions to the RPN calculator, and add some more primitives like triginometry functions and things like truncate, floor etc. More primitives =============== We need some more primitives I think to round out the basic functionality of the calculator, so I will add the functions /sin/, /cos/, /tan/, /truncate/, /floor/ and /ceiling/. But first I want to re-write /rpn-func/, so that it has another argument, the number of arguments the function that is passed to it takes, so that we can use it for functions like /fact/ that take one variable. ,---- | (define (rpn-func func args stack) | (if (= args 1) | (let-values (((var stack) (pop stack))) | (push (func var) stack)) | (let*-values (((var1 stack) (pop stack)) | ((var2 stack) (pop stack))) | (push (func var1 var2) stack)))) `---- I would love to be able to write a better version of this function, it currently only works with functions that take one argument or two, but I would lave to write a general version that can run a function of arbitrary arguments. It would probably have to be a macro that expanded to a function that just had a bunch of nested /let-values/ and ran the func at the end with required funcs. When expanded it would look something like this, if the function took two arguments. ,---- | ;; Not working code | (lambda (stack) | (let-values (((var1 stack) (pop stack))) | (let-values (((var2 stack) (pop stack))) | (push (func var1 var2) stack)))) ;; func being the function | ;; passed to the macoro `---- But alas, maybe something to work on later. Or if anyone has any ideas that would be sick too. Any way here is our modified initial dictionary. ,---- | (define (swap stack) | (let ((a (car stack)) | (b (cadr stack))) | (append (list b) (list a) (cddr stack)))) | | (define init-dict | (list (cons '$ (lambda (stack) | (let-values (((var stack) (pop stack))) | (display var) | (newline) | stack))) | (cons '+ (lambda (stack) (rpn-func + 2 stack))) | (cons '- (lambda (stack) (rpn-func - 2 stack))) | (cons '* (lambda (stack) (rpn-func * 2 stack))) | (cons '/ (lambda (stack) (rpn-func / 2 stack))) | (cons '% (lambda (stack) (rpn-func modulo 2 stack))) | (cons '! (lambda (stack) (rpn-func fact 1 stack))) | (cons 'dup (lambda (stack) (dup stack))) | (cons 'swap (lambda (stack) (swap stack))) | (cons 'sin (lambda (stack) (rpn-func sin 1 stack))) | (cons 'cos (lambda (stack) (rpn-func cos 1 stack))) | (cons 'tan (lambda (stack) (rpn-func tan 1 stack))) | (cons 'trunc (lambda (stack) (rpn-func truncate 1 stack))) | (cons 'ceil (lambda (stack) (rpn-func ceiling 1 stack))) | (cons 'floor (lambda (stack) (rpn-func floor 1 stack))))) `---- User defined functions (attempt 1) ================================== How we are going to approach user defined functions is I think, pretty sweet. If we enter a list at our command line, the /read/ functions passes the whole thing along in one go, so we can have a predicate on our main loop that checks if the input is a list, and then pass that list to the function that will add it to the dictionary. We will use a macro to construct a series of embedded /let/ forms that execute each function in turn, or push the number onto the stack, so we can have constants in user defined functions. Unfortunately we have to change our design sligtly by passing the dictionary to every RPN function call, beacause new user functions need to refer to the primitives, which can only happen if when executing the dictionary is available. So now every function in the dictionary takes two arguments, the stack and the dictionary. Which isn't great, and I could avoid that by having two dictionaries, the primitives and the user. But I would prefer to have only one dictionary, so I have to do the ugly thing. ,---- | (define (new-func list dictionary) | (insert-into-alist (car list) (user-func-from-list (cdr list)) dictionary)) `---- user-func-from-list ~~~~~~~~~~~~~~~~~~~ Now this macro will take the list, minus the head, because that will be the identifier for the function. And return our function. The macro will have to to expand from this: ,---- | (dup cos swap sin /) `---- To this: ,---- | (lambda (stack dict) | (let ((stack (run-func '/ dict stack))) | (let ((stack (run-func 'sin dict stack))) | (let ((stack (run-func 'swap dict stack))) | (let ((stack (run-func 'cos dict stack))) | (let ((stack (run-func 'dup dict stack))) | stack)))))) `---- ,---- | (define-syntax user-func-from-list | (syntax-rules () | ((user-func-from-list () form . forms) | '(lambda (stack dict) form . forms)) | | ((user-func-from-list (variable) form . forms) | (user-func-from-list () '(let ((stack (run-func (quote variable) dict stack))) form . forms))) | | ((user-func-from-list (variable . variables) form . forms) | (user-func-from-list variables '(let ((stack (run-func (quote variable) dict stack))) form . forms))) | | ((user-func-from-list (variable . variables)) | (user-func-from-list variables '(let ((stack (run-func (quote variable) dict stack))) stack))) | | ((user-func-from-list (quote (variable . variables))) | (user-func-from-list variables '(let ((stack (run-func (quote variable) dict stack))) stack))))) `---- Now the reason that this is attempt is a failure is I was trying to use a macro at runtime instead of expansion/compile time. So this isn't going to work unfortunately, but a good learning experience I think. User defined functions (Attempt 2) ================================== So this time around we are going to keep /new-func/ as it is, but /user-func-from-list/ will be a function that returns a function. And that function will be the same as the ones in our dictionary, taking the stack, but we will have to modify all of our dictionary functions to also take the dictionary, so that our user functions can refer to all the previously defined functions. The function will loop over the list and execute each function in turn, and when the list runs out of functions, return the stack. ,---- | (define (user-func-from-list func) | (lambda (stack dict) | (let loop ((func func) | (stack stack)) | (if (= (length func) 1) | (if (number? (car func)) | (push (car func) stack) | (run-func (car func) dict stack)) | (if (number? (car func)) | (loop (cdr func) (push (car func) stack)) | (loop (cdr func) (run-func (car func) dict stack))))))) `---- And now we just have to change the main loop to test for lists, and pass the list to /new-func/. ,---- | (let loop ((input (read)) | (stack '()) | (dict init-dict)) | (cond | ((number? input) (loop (read) (push input stack) dict)) | ((list? input) (loop (read) stack (new-func input 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))))) `---- And thats it! You can define new functions on the command line, but I should probably clean up things and maybe make it so you can save those user defined functions...