__________________________
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,
[email protected].
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.