__________________________ CLEANING UP THE RPN CALC lro __________________________ <2019-01-07 Mon> Table of Contents _________________ Current architecture Saving user functions So Last post I alluded that I was going to clean things things up a bit and add a way to save any user define functions. Lets have a general look at how things work at the moment, then go from there. Current architecture ==================== At the moment, when the main loop encounters a function, it calls /run-func/ which checks if the `func' is the dictionary, and if it is run it otherwise print an error. And when the function is called, if its a primitive, the function is /rpn-func/, which takes a scheme function and how many arguments it needs, then /pop/s the appropriate number of arguments and calls the function on tose arguments and /push/es the result to the stack that is then returned. If a user defined function is called, then the list of functions is executed using /run-func/, and the altered stack passed along to the next function in the list. ,---- | +----------+ number +------------+ list +----------+ | +---| push |<------------| Main loop |-------->| new-func |----| | | +----------+ +------------+ +----------+ | | | ^ | ^ ^ | | +-----------------------------+ | | |--------------------------| | | | | | | | symbol | ------| | | | | +----------+ | | | run-func | | | +----------+ | | | | | | | | +------+ | | | func | | | +------+ | | | | | | | | +----------+ | | | rpn-func |---- | +----------+ `---- <file:rpn-calc-func-call-diagram.png> So its not terribly complex, but I would prefer if calling a function didn't jump thorugh so many hoops. But it isn't really that much abstraction. If I wanted to remove, I could write /rpn-func/ as a macro that expanded to a lambda that was juct the appropiate /let-values/ for many arguments the function took. For example: ,---- | (rpn-func + 2) `---- Would expand too: ,---- | (lambda (stack dict) | (let*-values (((var1 stack) (pop stack)) | ((var2 stack) (pop stack))) | (push (+ var1 var2) stack))) `---- Which would be awesome, but I don't know if it's worth it. For now I think i'll just keep things as they are with regards to function calls. I don't want to use macros to much, I think it would be best to try and keep them to a minimum, so I'm not going to build `init-dict' at compile time either, maybe in my next phlog post as a little detour I can go ham on the macros, both as practice and something interesting to phlog about. Saving user functions ===================== Looking at how I add user functions to the dictionary, it would be pretty easy to add some code to /new-func/ that writes the user functions to a file like /user-funcs/ and just write the lists to it. Then when we start the main loop, we can call a function that finds this file, and loads all the functions into the dictionary at start up. We can abuse/use read again by using /parameterize/ to make the standard input the /user-funcs/ file and add all the functions at startup using /new-func/. Unfortunately, there is no standard way in R7RS to append to a file, what bullshit. So we have some options: 1. read through the whole /user-funcs/ to get to the end. (slow) 2. just overwrite the file everytime we save. (stupid) 3. read /user-funcs/ in at startup, save to a string, and append new functions to that string, and saves that string to the file. We will go with number 3. So firstly we need a function to add a new user func to the /user-funcs/ string. I have used /parameterize/ so that I can use /display/ which will print our newlines correctly so that "your-funcs" has each function on a line. It then _overwrites_ the file with the old funcs and the new func, and returns that string as well. ,---- | (define (add-user-func list user-funcs file) | (let ((func-to-add (list-as-string list))) | (parameterize ((current-output-port (open-output-file file))) | (let ((new-user-funcs (string-append user-funcs func-to-add "\n"))) | (display new-user-funcs) | (close-output-port (current-output-port)) | new-user-funcs)))) `---- I needed a function that converted lists to a string, because schemes /list->string/ just converts a list of chars to one string. ,---- | (define (list-as-string list) | (parameterize ((current-output-port (open-output-string))) | (write list) | (get-output-string (current-output-port)))) `---- Now we can enter new functions to our save file, which is held in a variable. ,---- | (define funcs-file "your-funcs") `---- And now we need functions to retrieve the functions from the save file at the start of the main loop. ,---- | (define (load-funcs-from-file-dict file dict) | (with-input-from-file file | (lambda () | (let loop ((input (read)) | (dict dict)) | (if (eof-object? input) | dict | (loop (read) (new-func input dict))))))) | | (define (load-funcs-from-file-str file) | (with-input-from-file file | (lambda () | (let loop ((next-str (read-string 10)) | (str "")) | (if (eof-object? next-str) | str | (loop (read-string 10) (string-append str next-str))))))) `---- And our new main loop ,---- | (let loop ((input (read)) | (stack '()) | (dict (load-funcs-from-file-dict funcs-file init-dict)) | (user-funcs (load-funcs-from-file-str funcs-file))) | (cond | ((number? input) (loop (read) (push input stack) dict user-funcs)) | ((list? input) (let ((user-funcs (add-user-func input user-funcs funcs-file))) | (loop (read) stack (new-func input dict) user-funcs))) | ((symbol? input) (loop (read) (run-func input dict stack) dict user-funcs)) | (else (begin | (display "ERROR not valid input: ") | (display input) | (newline) | (loop (read) stack dict))))) `---- I also found a bud in my /update-alist/ function, it added an extra layer of parenthesis when updating functions. ,---- | (define (update-alist key new-val alist) | (let ((index (index-in-alist key alist))) | (list-set! alist index (cons key new-val)) | alist)) `---- So thats it for now, if anyone has any feature suggestions or patches or bug reports, feel free to email me, lro@sdf.org. Next time we might do some macro programming, or just actually go through and use this thing and do some cool RPN maths programming, we'll see.